From b09be09fcf3e07924ebc4363385ca3d40eca2eaf Mon Sep 17 00:00:00 2001 From: Max Qian Date: Wed, 6 Dec 2023 14:37:49 +0800 Subject: [PATCH 1/9] update - not finished --- modules/CMakeLists.txt | 4 +- modules/lithium_image/CMakeLists.txt | 62 +++- modules/lithium_image/{ => cimg}/draw.cpp | 1 - .../{lithium_image.cpp => cimg/image.cpp} | 4 +- .../{lithium_image.hpp => cimg/image.hpp} | 4 +- modules/lithium_image/config.h.in | 3 + modules/lithium_image/config.json | 20 ++ modules/lithium_image/main.cpp | 59 +++ .../{cvimage.cpp => opencv/image.cpp} | 0 .../{cvimage.hpp => opencv/image.hpp} | 0 .../{image.cpp => opencv/pimage.cpp} | 0 modules/lithium_search/CMakeLists.txt | 126 +++++++ modules/lithium_search/config.h.in | 3 + modules/lithium_search/config.json | 20 ++ .../main.cpp} | 6 +- modules/lithium_search/ongc.hpp | 167 --------- modules/lithium_search/ongc/dso.cpp | 244 +++++++++++++ modules/lithium_search/ongc/dso.hpp | 117 ++++++ modules/lithium_search/{ => ongc}/ongc.cpp | 21 +- .../ongc/ongc.hpp} | 25 +- modules/lithium_search/ongc/ongc_utils.cpp | 69 ++++ modules/lithium_search/ongc/ongc_utils.hpp | 7 + src/ErrorHandler.cpp | 205 ++++++++--- src/LithiumApp.cpp | 181 +++++++++- src/LithiumApp.hpp | 24 ++ src/atom/CMakeLists.txt | 4 + src/atom/plugin/compiler.cpp | 244 +++++++++++-- src/atom/plugin/compiler.hpp | 25 +- src/atom/plugin/module_loader.cpp | 320 ++++++++++------- src/atom/plugin/module_loader.hpp | 203 +++++++---- src/atom/search/mysql.cpp | 334 +++++++++++++++++ src/atom/search/mysql.hpp | 106 ++++++ src/atom/search/sqlite.cpp | 248 +++++++++++++ src/atom/search/sqlite.hpp | 218 +++++++++++ src/atom/server/search.cpp | 337 ++++++++++++++++++ src/atom/server/search.hpp | 304 ++++++++++++++++ src/controller/AsyncModuleController.hpp | 272 +++++++++++++- src/controller/template/variable.hpp | 5 + src/data/ModuleDto.hpp | 188 +++++++++- 39 files changed, 3673 insertions(+), 507 deletions(-) rename modules/lithium_image/{ => cimg}/draw.cpp (99%) rename modules/lithium_image/{lithium_image.cpp => cimg/image.cpp} (99%) rename modules/lithium_image/{lithium_image.hpp => cimg/image.hpp} (98%) create mode 100644 modules/lithium_image/config.h.in create mode 100644 modules/lithium_image/config.json create mode 100644 modules/lithium_image/main.cpp rename modules/lithium_image/{cvimage.cpp => opencv/image.cpp} (100%) rename modules/lithium_image/{cvimage.hpp => opencv/image.hpp} (100%) rename modules/lithium_image/{image.cpp => opencv/pimage.cpp} (100%) create mode 100644 modules/lithium_search/CMakeLists.txt create mode 100644 modules/lithium_search/config.h.in create mode 100644 modules/lithium_search/config.json rename modules/{lithium_image/lithium_image_main.cpp => lithium_search/main.cpp} (89%) delete mode 100644 modules/lithium_search/ongc.hpp create mode 100644 modules/lithium_search/ongc/dso.cpp create mode 100644 modules/lithium_search/ongc/dso.hpp rename modules/lithium_search/{ => ongc}/ongc.cpp (97%) rename modules/{lithium_image/image.hpp => lithium_search/ongc/ongc.hpp} (68%) create mode 100644 modules/lithium_search/ongc/ongc_utils.cpp create mode 100644 modules/lithium_search/ongc/ongc_utils.hpp create mode 100644 src/atom/search/mysql.cpp create mode 100644 src/atom/search/mysql.hpp create mode 100644 src/atom/search/sqlite.cpp create mode 100644 src/atom/search/sqlite.hpp create mode 100644 src/atom/server/search.cpp create mode 100644 src/atom/server/search.hpp create mode 100644 src/controller/template/variable.hpp diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 51d63365..aa349db4 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -1 +1,3 @@ -add_subdirectory(lithium_image) \ No newline at end of file +add_subdirectory(lithium_image) + +add_subdirectory(lithium_search) \ No newline at end of file diff --git a/modules/lithium_image/CMakeLists.txt b/modules/lithium_image/CMakeLists.txt index 5b5894b6..822f991e 100644 --- a/modules/lithium_image/CMakeLists.txt +++ b/modules/lithium_image/CMakeLists.txt @@ -1,6 +1,33 @@ cmake_minimum_required(VERSION 3.20) project(lithium_image C CXX) +option(ENABLE_DEBUG "Enable Debug Mode" OFF) +if(ENABLE_DEBUG) + set(ENABLE_DEBUG_FLAG "1") +endif() + +option(ENABLE_OPENCV_FLAG "Enable OpenCV" OFF) +if (ENABLE_OPENCV_FLAG) + find_package(OpenCV REQUIRED) + message("-- Found OpenCV ${OpenCV_VERSION}: ${OpenCV_LIBRARIES}") +else() + message("-- OpenCV Not Found. Will not build opencv image module.") +endif() + + +option(ENABLE_CIMG_FLAG "Enable CImg" On) +if(ENABLE_CIMG_FLAG) + set(ENABLE_CIMG "1") +endif() + +option(ENABLE_STB_FLAG "Enable STB" OFF) +if(ENABLE_STB_FLAG) + set(ENABLE_STB "1") +endif() + +configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + CHECK_INCLUDE_FILE(format HAS_STD_FORMAT) if(NOT HAS_STD_FORMAT) @@ -13,16 +40,39 @@ list(APPEND ${PROJECT_NAME}_LIBS ${CFITSIO_LIBRARIES} ) +if(ENABLE_OPENCV_FLAG) + list(APPEND ${PROJECT_NAME}_LIBS + ${OpenCV_LIBRARIES} + ) +endif() # Sources list(APPEND ${PROJECT_NAME}_SOURCES - lithium_image.cpp - lithium_image_main.cpp + main.cpp ) -# Headers -list(APPEND ${PROJECT_NAME}_HEADERS - lithium_image.hpp -) +if(ENABLE_OPENCV_FLAG) + list(APPEND ${PROJECT_NAME}_SOURCES + opencv/image.cpp + ) +endif() + +if(ENABLE_CIMG_FLAG) + list(APPEND ${PROJECT_NAME}_SOURCES + cimg/image.cpp + ) +endif() + +if(ENABLE_OPENCV_FLAG) + list(APPEND ${PROJECT_NAME}_HEADERS + opencv/image.hpp + ) +endif() + +if(ENABLE_CIMG_FLAG) + list(APPEND ${PROJECT_NAME}_HEADERS + cimg/image.hpp + ) +endif() # Private Headers list(APPEND ${PROJECT_NAME}_PRIVATE_HEADERS diff --git a/modules/lithium_image/draw.cpp b/modules/lithium_image/cimg/draw.cpp similarity index 99% rename from modules/lithium_image/draw.cpp rename to modules/lithium_image/cimg/draw.cpp index 1a4e315c..96e021c3 100644 --- a/modules/lithium_image/draw.cpp +++ b/modules/lithium_image/cimg/draw.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include "image.hpp" diff --git a/modules/lithium_image/lithium_image.cpp b/modules/lithium_image/cimg/image.cpp similarity index 99% rename from modules/lithium_image/lithium_image.cpp rename to modules/lithium_image/cimg/image.cpp index 71882ec8..e79b7085 100644 --- a/modules/lithium_image/lithium_image.cpp +++ b/modules/lithium_image/cimg/image.cpp @@ -1,5 +1,5 @@ /* - * lithium_image.cpp + * image.cpp * * Copyright (C) 2023 Max Qian * @@ -29,7 +29,7 @@ Description: Image processing plugin for Lithium **************************************************/ -#include "lithium_image.hpp" +#include "image.hpp" #include #include diff --git a/modules/lithium_image/lithium_image.hpp b/modules/lithium_image/cimg/image.hpp similarity index 98% rename from modules/lithium_image/lithium_image.hpp rename to modules/lithium_image/cimg/image.hpp index 1a5fddeb..30c42afa 100644 --- a/modules/lithium_image/lithium_image.hpp +++ b/modules/lithium_image/cimg/image.hpp @@ -1,5 +1,5 @@ /* - * lithium_image.hpp + * image.hpp * * Copyright (C) 2023 Max Qian * @@ -29,6 +29,8 @@ Description: Image processing plugin for Lithium **************************************************/ +#pragma once + #include "core/plugin/plugin.hpp" #include "cimg/CImg.h" #include diff --git a/modules/lithium_image/config.h.in b/modules/lithium_image/config.h.in new file mode 100644 index 00000000..75288ee6 --- /dev/null +++ b/modules/lithium_image/config.h.in @@ -0,0 +1,3 @@ +#cmakedefine01 ENABLE_OPENCV +#cmakedefine01 ENABLE_CIMG +#cmakedefine01 ENABLE_STB \ No newline at end of file diff --git a/modules/lithium_image/config.json b/modules/lithium_image/config.json new file mode 100644 index 00000000..046aca97 --- /dev/null +++ b/modules/lithium_image/config.json @@ -0,0 +1,20 @@ +{ + "name": "Image Processing Module for Lithium", + "description": "This module provides image processing functionality for Lithium.", + "version": "1.0.0", + "author": "Max Qian", + "license": "GPLv3 -or- later", + "dependencies": { + "lithium-plugin": "1.0.0", + "atom-core": "1.0.0" + }, + "main": "lithium_image", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ElementAstro/Lithium/modules/lithium_image" + }, + "keywords": ["lithium", "image", "processing"] +} diff --git a/modules/lithium_image/main.cpp b/modules/lithium_image/main.cpp new file mode 100644 index 00000000..a7f0f243 --- /dev/null +++ b/modules/lithium_image/main.cpp @@ -0,0 +1,59 @@ +/* + * lithium_image_main.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-1 + +Description: Image processing plugin main + +**************************************************/ + +#include "config.h" + +#if ENABLE_CIMG +#include "cimg/image.hpp" +#endif +#if ENABLE_OPENCV +#include "opencv/image.hpp" +#endif +#include + +std::shared_ptr GetInstance() +{ + return std::make_shared("lithium_image", "1.0.0", "Max Qian", "Image processing plugin"); +} + +json GetInfo() +{ + json config; + config["name"] = "lithium_image"; + config["version"] = "1.0.0"; + config["description"] = "Image processing plugin"; + config["author"] = "Max Qian"; + config["email"] = "astro_air@126.com"; + config["url"] = "lightapt.com"; + config["license"] = "GPLv3"; + config["copyright"] = "2023 Max Qian. All rights reserved"; + return config; +} \ No newline at end of file diff --git a/modules/lithium_image/cvimage.cpp b/modules/lithium_image/opencv/image.cpp similarity index 100% rename from modules/lithium_image/cvimage.cpp rename to modules/lithium_image/opencv/image.cpp diff --git a/modules/lithium_image/cvimage.hpp b/modules/lithium_image/opencv/image.hpp similarity index 100% rename from modules/lithium_image/cvimage.hpp rename to modules/lithium_image/opencv/image.hpp diff --git a/modules/lithium_image/image.cpp b/modules/lithium_image/opencv/pimage.cpp similarity index 100% rename from modules/lithium_image/image.cpp rename to modules/lithium_image/opencv/pimage.cpp diff --git a/modules/lithium_search/CMakeLists.txt b/modules/lithium_search/CMakeLists.txt new file mode 100644 index 00000000..c5617477 --- /dev/null +++ b/modules/lithium_search/CMakeLists.txt @@ -0,0 +1,126 @@ +cmake_minimum_required(VERSION 3.20) +project(lithium_search C CXX) + +option(ENABLE_DEBUG "Enable Debug Mode" OFF) +if(ENABLE_DEBUG) + set(ENABLE_DEBUG_FLAG "1") +endif() + +option(ENABLE_SQLITE_FLAG "Enable Sqlite" ON) +if (ENABLE_SQLITE_FLAG) + find_package(SQLite3 REQUIRED) + message("-- Found SQLite3 ${SQLite3_VERSION}: ${SQLite3_LIBRARIES}") + set(ENABLE_SQLITE "1") +else() + message("-- SQLite3 Not Found. Will not build sqlite search module.") +endif() + +option(ENABLE_MYSQL_FLAG "Enable MySQL" OFF) +if (ENABLE_MYSQL_FLAG) + find_package(MySQL REQUIRED) + message("-- Found MySQL ${MySQL_VERSION}: ${MySQL_LIBRARIES}") + set(ENABLE_MYSQL "1") +else() + message("-- MySQL Not Found. Will not build mysql search module.") +endif() + +option(ENABLE_POSTGRESQL_FLAG "Enable PostgreSQL" OFF) +if (ENABLE_POSTGRESQL_FLAG) + find_package(PostgreSQL REQUIRED) + message("-- Found PostgreSQL ${PostgreSQL_VERSION}: ${PostgreSQL_LIBRARIES}") + set(ENABLE_POSTGRESQL "1") +else() + message("-- PostgreSQL Not Found. Will not build postgresql search module.") +endif() + +configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +CHECK_INCLUDE_FILE(format HAS_STD_FORMAT) + +if(NOT HAS_STD_FORMAT) + find_package(fmt REQUIRED) +endif() + +list(APPEND ${PROJECT_NAME}_LIBS + lithiumpluginstatic + loguru + ${CFITSIO_LIBRARIES} +) + +if(ENABLE_SLQLITE_FLAG) + list(APPEND ${PROJECT_NAME}_LIBS + ${SQLite3_LIBRARIES} + ) +endif() + +if(ENABLE_MYSQL_FLAG) + list(APPEND ${PROJECT_NAME}_LIBS + ${MySQL_LIBRARIES} + ) +endif() + +if(ENABLE_POSTGRESQL_FLAG) + list(APPEND ${PROJECT_NAME}_LIBS + ${PostgreSQL_LIBRARIES} + ) +endif() +# Sources +list(APPEND ${PROJECT_NAME}_SOURCES + main.cpp + + ongc/dso.cpp + ongc/ongc.cpp + +) + +# Private Headers +list(APPEND ${PROJECT_NAME}_PRIVATE_HEADERS + +) + +# Build Object Library +add_library(${PROJECT_NAME}_OBJECT OBJECT) +set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) + +target_link_libraries(${PROJECT_NAME}_OBJECT loguru lithiumpluginstatic) + +target_sources(${PROJECT_NAME}_OBJECT + PUBLIC + ${${PROJECT_NAME}_HEADERS} + PRIVATE + ${${PROJECT_NAME}_SOURCES} + ${${PROJECT_NAME}_PRIVATE_HEADERS} +) + +target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) + +add_library(${PROJECT_NAME}static STATIC) + +target_link_libraries(${PROJECT_NAME}static ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) +target_link_libraries(${PROJECT_NAME}static ${CMAKE_THREAD_LIBS_INIT}) +target_include_directories(${PROJECT_NAME}static PUBLIC .) + +set_target_properties(${PROJECT_NAME}static PROPERTIES + VERSION ${CMAKE_HYDROGEN_VERSION_STRING} + SOVERSION ${HYDROGEN_SOVERSION} + OUTPUT_NAME ${PROJECT_NAME} # this same name like shared library - backwards compatibility +) + +install(TARGETS ${PROJECT_NAME}static + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +add_library(${PROJECT_NAME}shared SHARED) + +target_link_libraries(${PROJECT_NAME}shared ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) +target_link_libraries(${PROJECT_NAME}shared ${CMAKE_THREAD_LIBS_INIT}) +target_include_directories(${PROJECT_NAME}shared PUBLIC .) +set_target_properties(${PROJECT_NAME}shared PROPERTIES + VERSION ${CMAKE_HYDROGEN_VERSION_STRING} + SOVERSION ${HYDROGEN_SOVERSION} + OUTPUT_NAME ${PROJECT_NAME} # this same name like shared library - backwards compatibility +) +install(TARGETS ${PROJECT_NAME}shared + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) \ No newline at end of file diff --git a/modules/lithium_search/config.h.in b/modules/lithium_search/config.h.in new file mode 100644 index 00000000..3397b2be --- /dev/null +++ b/modules/lithium_search/config.h.in @@ -0,0 +1,3 @@ +#cmakedefine01 ENABLE_SQLITE +#cmakedefine01 ENABLE_MYSQL +#cmakedefine01 ENABLE_POSTGRESQL \ No newline at end of file diff --git a/modules/lithium_search/config.json b/modules/lithium_search/config.json new file mode 100644 index 00000000..5fe5b59a --- /dev/null +++ b/modules/lithium_search/config.json @@ -0,0 +1,20 @@ +{ + "name": "Star Search Engine for Lithium", + "description": "Search engine for Lithium", + "version": "1.0.0", + "author": "Max Qian", + "license": "GPLv3 -or- later", + "dependencies": { + "lithium-plugin": "1.0.0", + "atom-core": "1.0.0" + }, + "main": "lithium_search", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ElementAstro/Lithium/modules/lithium_search" + }, + "keywords": ["lithium", "openngc", "star", "sqlite", "mysql"] +} diff --git a/modules/lithium_image/lithium_image_main.cpp b/modules/lithium_search/main.cpp similarity index 89% rename from modules/lithium_image/lithium_image_main.cpp rename to modules/lithium_search/main.cpp index fca3a024..f55980af 100644 --- a/modules/lithium_image/lithium_image_main.cpp +++ b/modules/lithium_search/main.cpp @@ -29,13 +29,15 @@ Description: Image processing plugin main **************************************************/ -#include "lithium_image.hpp" +#include "config.h" + +#include "ongc/ongc.hpp" #include std::shared_ptr GetInstance() { - return std::make_shared(); + return std::make_shared("lithium_image", "1.0.0", "Max Qian", "Image processing plugin"); } json GetInfo() diff --git a/modules/lithium_search/ongc.hpp b/modules/lithium_search/ongc.hpp deleted file mode 100644 index d1fcd1c1..00000000 --- a/modules/lithium_search/ongc.hpp +++ /dev/null @@ -1,167 +0,0 @@ -/* - * ongc.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: C++ Version of PyOngc - -**************************************************/ - -#pragma once - -#include -#include -#include - -/** - * @class Dso - * @brief 表示一个天体对象 (Distributed Star Object) - */ -class Dso -{ -public: - /** - * @brief 构造函数 - * @param name 天体对象的名称 - * @param returndup 是否返回复制对象 - */ - Dso(const std::string &name, bool returndup = false); - - /** - * @brief 将天体对象转换为字符串表示形式 - * @return 天体对象的字符串表示形式 - */ - std::string to_string() const; - - /** - * @brief 获取天体对象所属的星座 - * @return 星座的名称 - */ - std::string get_constellation() const; - - /** - * @brief 获取天体对象的坐标 - * @return 包含赤经和赤纬的坐标数组 - */ - std::vector get_coords() const; - - /** - * @brief 获取天体对象的赤纬 - * @return 赤纬的字符串表示形式 - */ - std::string get_dec() const; - - /** - * @brief 获取天体对象的尺寸 - * @return 包含主轴长度、次轴长度和位置角的尺寸数组 - */ - std::vector get_dimensions() const; - - /** - * @brief 获取天体对象的哈勃分类 - * @return 哈勃分类的字符串表示形式 - */ - std::string get_hubble() const; - - /** - * @brief 获取天体对象的ID - * @return 天体对象的ID - */ - int get_id() const; - - /** - * @brief 获取天体对象的所有标识符 - * @return 返回一个包含多个标识符的元组,依次是目录、Messier编号、NGC编号、IC编号和常用名称 - */ - std::tuple, std::vector, std::vector, std::vector> get_identifiers() const; - -private: - /** - * @brief 识别天体对象的名称 - * @param name 天体对象的名称 - * @param catalog 识别出的目录名称 - * @param objectname 识别出的目录中的对象名称 - */ - void recognize_name(const std::string &name, std::string &catalog, std::string &objectname) const; - - /** - * @brief 查询数据库并返回单行结果 - * @param cols 查询的列名 - * @param tables 查询的表名 - * @param params 查询参数 - * @return 返回查询结果的字符串数组 - */ - std::vector _queryFetchOne(const std::string &cols, const std::string &tables, const std::string ¶ms); - - /** - * @brief 将字符串按指定分隔符拆分为字符串数组 - * @param input 输入字符串 - * @param delimiter 分隔符 - * @return 拆分后的字符串数组 - */ - std::vector split(const std::string &input, char delimiter) const; - - /** - * @brief 将字符串数组按指定分隔符连接成一个字符串 - * @param strings 字符串数组 - * @param delimiter 分隔符 - * @return 连接后的字符串 - */ - std::string join(const std::vector &strings, const std::string &delimiter) const; - - int _id; /**< 天体对象的ID */ - std::string _name; /**< 天体对象的名称 */ - std::string _type; /**< 天体对象的类型 */ - double _ra; /**< 天体对象的赤经 */ - double _dec; /**< 天体对象的赤纬 */ - std::string _const; /**< 天体对象所属的星座 */ - std::string _notngc; /**< 是否不属于NGC目录 */ - double _majax; /**< 天体对象的主轴长度 */ - double _minax; /**< 天体对象的次轴长度 */ - int _pa; /**< 天体对象的位置角 */ - double _bmag; /**< 天体对象的B星等 */ - double _vmag; /**< 天体对象的V星等 */ - double _jmag; /**< 天体对象的J星等 */ - double _hmag; /**< 天体对象的H星等 */ - double _kmag; /**< 天体对象的K星等 */ - double _sbrightn; /**< 天体对象的表面亮度 */ - std::string _hubble; /**< 天体对象的哈勃分类 */ - double _parallax; /**< 天体对象的视差 */ - double _pmra; /**< 天体对象的赤经年周运动 */ - double _pmdec; /**< 天体对象的赤纬年周运动 */ - double _radvel; /**< 天体对象的径向速度 */ - double _redshift; /**< 天体对象的红移 */ - double _cstarumag; /**< 中心恒星的U星等 */ - double _cstarbmag; /**< 中心恒星的B星等 */ - double _cstarvmag; /**< 中心恒星的V星等 */ - std::string _messier; /**< 天体对象的Messier编号 */ - std::string _ngc; /**< 天体对象的NGC编号 */ - std::string _ic; /**< 天体对象的IC编号 */ - std::string _cstarnames; /**< 中心恒星的名称 */ - std::string _identifiers; /**< 天体对象的标识符 */ - std::string _commonnames; /**< 天体对象的常用名称 */ - std::string _nednotes; /**< 天体对象的NED注释 */ - std::string _ongcnotes; /**< 天体对象的OpenNGC注释 */ -}; diff --git a/modules/lithium_search/ongc/dso.cpp b/modules/lithium_search/ongc/dso.cpp new file mode 100644 index 00000000..a1238ada --- /dev/null +++ b/modules/lithium_search/ongc/dso.cpp @@ -0,0 +1,244 @@ +#include "dso.hpp" + +#include +#include + +DsoObject::DsoObject(const std::string &name) +{ + if (name.empty()) + { + throw std::invalid_argument("Name parameter cannot be empty."); + } + + m_id = 0; + m_name = name; + m_type = "Unknown"; + m_ra = 0.0; + m_dec = 0.0; + m_const = "Unknown"; + m_notngc = "Unknown"; + m_majax = 0.0; + m_minax = 0.0; + m_pa = 0; + m_bmag = 0.0; + m_vmag = 0.0; + m_jmag = 0.0; + m_hmag = 0.0; + m_kmag = 0.0; + m_sbrightn = 0.0; + m_parallax = 0.0; + m_pmra = 0.0; + m_pmdec = 0.0; + m_radvel = 0.0; + m_redshift = 0.0; + m_cstarumag = 0.0; + m_cstarbmag = 0.0; + m_cstarvmag = 0.0; + + std::string catalog, objectname; + std::string tables = "objects JOIN objTypes ON objects.type = objTypes.type "; + tables += "JOIN objIdentifiers ON objects.name = objIdentifiers.name"; + recognize_name(name, catalog, objectname); + std::stringstream params; + + if (catalog == "Messier") + { + params << "messier=\"" << objectname << "\""; + } + else + { + params << "objIdentifiers.identifier=\"" << objectname << "\""; + } + + // Perform query and retrieve object data + std::vector objectData = _queryFetchOne(catalog, tables, params.str()); + + if (objectData.empty()) + { + throw std::runtime_error("Object not found: " + objectname); + } + + // If object is a duplicate and returndup is false, get the main object data + if (objectData[2] == "Dup" && !returndup) + { + if (!objectData[26].empty()) + { + objectname = "NGC" + objectData[26]; + } + else + { + objectname = "IC" + objectData[27]; + } + objectData = _queryFetchOne(catalog, tables, params.str()); + } + + // Assign object properties + _id = std::stoi(objectData[0]); + _name = objectData[1]; + _type = objectData[3]; + _ra = std::stod(objectData[4]); + _dec = std::stod(objectData[5]); + _const = objectData[6]; + _notngc = objectData[33]; + + // Assign optional properties + if (!objectData[7].empty()) + { + _majax = std::stod(objectData[7]); + } + if (!objectData[8].empty()) + { + _minax = std::stod(objectData[8]); + } + if (!objectData[9].empty()) + { + _pa = std::stoi(objectData[9]); + } + if (!objectData[10].empty()) + { + _bmag = std::stod(objectData[10]); + } + if (!objectData[11].empty()) + { + _vmag = std::stod(objectData[11]); + } + if (!objectData[12].empty()) + { + _jmag = std::stod(objectData[12]); + } + if (!objectData[13].empty()) + { + _hmag = std::stod(objectData[13]); + } + if (!objectData[14].empty()) + { + _kmag = std::stod(objectData[14]); + } + if (!objectData[15].empty()) + { + _sbrightn = std::stod(objectData[15]); + } + _hubble = objectData[16]; + if (!objectData[17].empty()) + { + _parallax = std::stod(objectData[17]); + } + if (!objectData[18].empty()) + { + _pmra = std::stod(objectData[18]); + } + if (!objectData[19].empty()) + { + _pmdec = std::stod(objectData[19]); + } + if (!objectData[20].empty()) + { + _radvel = std::stod(objectData[20]); + } + if (!objectData[21].empty()) + { + _redshift = std::stod(objectData[21]); + } + if (!objectData[22].empty()) + { + _cstarumag = std::stod(objectData[22]); + } + if (!objectData[23].empty()) + { + _cstarbmag = std::stod(objectData[23]); + } + if (!objectData[24].empty()) + { + _cstarvmag = std::stod(objectData[24]); + } + _messier = objectData[25]; + _ngc = objectData[26]; + _ic = objectData[27]; + _cstarnames = objectData[28]; + _identifiers = objectData[29]; + _commonnames = objectData[30]; + _nednotes = objectData[31]; + _ongcnotes = objectData[32]; +} + +std::string Dso::to_string() const +{ + return _name + ", " + _type + " in " + _const; +} + +std::string Dso::get_constellation() const +{ + return _const; +} + +std::vector Dso::get_coords() const +{ + std::vector coords; + coords.push_back(std::trunc(rad2deg(_ra) / 15)); + double ms = (rad2deg(_ra) / 15 - coords[0]) * 60; + coords.push_back(std::trunc(ms)); + coords.push_back((ms - coords[1]) * 60); + + double dec = std::trunc(rad2deg(std::abs(_dec))); + ms = (rad2deg(std::abs(_dec)) - dec) * 60; + coords.push_back(dec * (_dec < 0 ? -1 : 1)); + coords.push_back(std::trunc(ms)); + coords.push_back((ms - coords[4]) * 60); + + return coords; +} + +std::string Dso::get_dec() const +{ + std::vector coords = get_coords(); + return std::to_string(coords[3]) + ":" + std::to_string(coords[4]) + ":" + std::to_string(coords[5]); +} + +std::vector Dso::get_dimensions() const +{ + return {_majax, _minax, static_cast(_pa)}; +} + +std::string Dso::get_hubble() const +{ + return _hubble; +} + +int Dso::get_id() const +{ + return _id; +} + +std::tuple, std::vector, std::vector, std::vector> Dso::get_identifiers() const +{ + std::string messier = (!_messier.empty()) ? "M" + _messier : ""; + std::vector ngc, ic, commonNames, other; + + if (!_ngc.empty()) + { + for (const auto &number : split(_ngc, ',')) + { + ngc.push_back("NGC" + number); + } + } + + if (!_ic.empty()) + { + for (const auto &number : split(_ic, ',')) + { + ic.push_back("IC" + number); + } + } + + if (!_commonnames.empty()) + { + commonNames = split(_commonnames, ','); + } + + if (!_identifiers.empty()) + { + other = split(_identifiers, ','); + } + + return std::make_tuple(messier, ngc, ic, commonNames, other); +} \ No newline at end of file diff --git a/modules/lithium_search/ongc/dso.hpp b/modules/lithium_search/ongc/dso.hpp new file mode 100644 index 00000000..cc50b6b5 --- /dev/null +++ b/modules/lithium_search/ongc/dso.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#if _cplusplus >= 201703L +#include +#endif + +/** + * @class DsoObject + * @brief 表示一个天体对象 (Distributed Star Object) + */ +class DsoObject +{ +public: + /** + * @brief 构造函数 + * @param name 天体对象的名称 + */ + DsoObject(const std::string &name); + + /** + * @brief 将天体对象转换为字符串表示形式 + * @return 天体对象的字符串表示形式 + */ + std::string toString() const; + + /** + * @brief 获取天体对象所属的星座 + * @return 星座的名称 + */ + std::string getConstellation() const; + + /** + * @brief 获取天体对象的坐标 + * @tparam T 坐标的类型 , std::vector or std::vector + * @return 天体对象的坐标 + */ + template +#if _cplusplus >= 202002L + requires std::is_arithmetic_v +#else + std::enable_if_t, std::vector> getCoords() const; +#endif + + /** + * @brief 获取天体对象的赤经 + * @return 赤经的字符串表示形式 + */ + std::string getRa() const; + + /** + * @brief 获取天体对象的赤纬 + * @return 赤纬的字符串表示形式 + */ + std::string getDec() const; + + /** + * @brief 获取天体对象的尺寸 + * @return 包含主轴长度、次轴长度和位置角的尺寸数组 + */ + std::vector getDimensions() const; + + /** + * @brief 获取天体对象的哈勃分类 + * @return 哈勃分类的字符串表示形式 + */ + std::string getHubble() const; + + /** + * @brief 获取天体对象的ID + * @return 天体对象的ID + */ + int getId() const; + + /** + * @brief 获取天体对象的所有标识符 + * @return 返回一个包含多个标识符的元组,依次是目录、Messier编号、NGC编号、IC编号和常用名称 + */ + std::tuple, std::vector, std::vector, std::vector> getIdentifiers() const; + +private: + int m_id; /**< 天体对象的ID */ + std::string m_name; /**< 天体对象的名称 */ + std::string m_type; /**< 天体对象的类型 */ + double m_ra; /**< 天体对象的赤经 */ + double m_dec; /**< 天体对象的赤纬 */ + std::string m_const; /**< 天体对象所属的星座 */ + std::string m_notngc; /**< 是否不属于NGC目录 */ + double m_majax; /**< 天体对象的主轴长度 */ + double m_minax; /**< 天体对象的次轴长度 */ + int m_pa; /**< 天体对象的位置角 */ + double m_bmag; /**< 天体对象的B星等 */ + double m_vmag; /**< 天体对象的V星等 */ + double m_jmag; /**< 天体对象的J星等 */ + double m_hmag; /**< 天体对象的H星等 */ + double m_kmag; /**< 天体对象的K星等 */ + double m_sbrightn; /**< 天体对象的表面亮度 */ + std::string m_hubble; /**< 天体对象的哈勃分类 */ + double m_parallax; /**< 天体对象的视差 */ + double m_pmra; /**< 天体对象的赤经年周运动 */ + double m_pmdec; /**< 天体对象的赤纬年周运动 */ + double m_radvel; /**< 天体对象的径向速度 */ + double m_redshift; /**< 天体对象的红移 */ + double m_cstarumag; /**< 中心恒星的U星等 */ + double m_cstarbmag; /**< 中心恒星的B星等 */ + double m_cstarvmag; /**< 中心恒星的V星等 */ + std::string m_messier; /**< 天体对象的Messier编号 */ + std::string m_ngc; /**< 天体对象的NGC编号 */ + std::string m_ic; /**< 天体对象的IC编号 */ + std::string m_cstarnames; /**< 中心恒星的名称 */ + std::string m_identifiers; /**< 天体对象的标识符 */ + std::string m_commonnames; /**< 天体对象的常用名称 */ + std::string m_nednotes; /**< 天体对象的NED注释 */ + std::string m_ongcnotes; /**< 天体对象的OpenNGC注释 */ +}; diff --git a/modules/lithium_search/ongc.cpp b/modules/lithium_search/ongc/ongc.cpp similarity index 97% rename from modules/lithium_search/ongc.cpp rename to modules/lithium_search/ongc/ongc.cpp index cd8ff4c3..1921cdb8 100644 --- a/modules/lithium_search/ongc.cpp +++ b/modules/lithium_search/ongc/ongc.cpp @@ -30,32 +30,25 @@ Description: C++ Version of PyOngc **************************************************/ #include "ongc.hpp" +#include "ongc_utils.hpp" -#include #include #include + +#include + #if ENABLE_FASTHASH #include "emhash/hash_table8.hpp" #else #include #endif -#include -#include "nlohmann/json.hpp" +#include "atom/type/json.hpp" +#include "atom/log/loguru.hpp" using json = nlohmann::json; #define DBPATH "ognc.db" -#ifndef M_PI -#define M_PI (3.14159265358979323846) -#endif - -double rad2deg(const double &radians) -{ - const double conversionFactor = 180.0 / M_PI; - return radians * conversionFactor; -} - template std::vector get_identifiers_helper(const std::tuple, std::vector, std::vector, std::vector> &identifiers) { @@ -305,7 +298,7 @@ std::tuple, std::vector, std: return std::make_tuple(messier, ngc, ic, commonNames, other); } -void Dso::recognize_name(const std::string &name, std::string &catalog, std::string &objectname) const +void DsoObject::recognizeName(const std::string &name, std::string &catalog, std::string &objectname) const { const std::unordered_map patterns = { {"NGC|IC", R"(^((?:NGC|IC)\s?)(\d{1,4})\s?((NED)(\d{1,2})|[A-Z]{1,2})?$)"}, diff --git a/modules/lithium_image/image.hpp b/modules/lithium_search/ongc/ongc.hpp similarity index 68% rename from modules/lithium_image/image.hpp rename to modules/lithium_search/ongc/ongc.hpp index 148135b5..4e2ad458 100644 --- a/modules/lithium_image/image.hpp +++ b/modules/lithium_search/ongc/ongc.hpp @@ -1,5 +1,5 @@ /* - * image.hpp + * ongc.hpp * * Copyright (C) 2023 Max Qian * @@ -23,13 +23,26 @@ Author: Max Qian E-mail: astro_air@126.com -Date: 2023-4-6 +Date: 2023-7-13 -Description: Image Processing +Description: C++ Version of PyOngc **************************************************/ -namespace Lithium +#pragma once + +#include "dso.hpp" +#include "core/plugin/plugin.hpp" + +class OpenNGC : public Plugin { - int StarDrawing(const std::string &filename, const unsigned int outerHfdDiameter = 50); -} // namespace Lithium +public: + OpenNGC(const std::string &path, const std::string &version, const std::string &author, const std::string &description); + ~OpenNGC(); + +private: + + void recognizeName(const std::string &name, std::string &catalog, std::string &objectname); + + +} \ No newline at end of file diff --git a/modules/lithium_search/ongc/ongc_utils.cpp b/modules/lithium_search/ongc/ongc_utils.cpp new file mode 100644 index 00000000..b122113d --- /dev/null +++ b/modules/lithium_search/ongc/ongc_utils.cpp @@ -0,0 +1,69 @@ +#include "ongc_utils.hpp" + +#include + +#ifndef M_PI +#define M_PI (3.14159265358979323846) +#endif + +double rad2deg(const double &radians) +{ + const double conversionFactor = 180.0 / M_PI; + return radians * conversionFactor; +} + +double deg2rad(const double °rees) +{ + const double conversionFactor = M_PI / 180.0; + return degrees * conversionFactor; +} + +template +T clamp(T value, T min, T max) +{ + if (value < min) + { + return min; + } + else if (value > max) + { + return max; + } + else + { + return value; + } +} + +template +T lerp(T start, T end, double t) +{ + return start + t * (end - start); +} + +double smoothstep(double edge0, double edge1, double x) +{ + x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); + return x * x * (3.0 - 2.0 * x); +} + +double normalizeAngle(double angle) +{ + while (angle <= -180.0) + { + angle += 360.0; + } + + while (angle > 180.0) + { + angle -= 360.0; + } + + return angle; +} + +double interpolateAngle(double start, double end, double t) +{ + double difference = normalizeAngle(end - start); + return start + difference * t; +} diff --git a/modules/lithium_search/ongc/ongc_utils.hpp b/modules/lithium_search/ongc/ongc_utils.hpp new file mode 100644 index 00000000..46879d7a --- /dev/null +++ b/modules/lithium_search/ongc/ongc_utils.hpp @@ -0,0 +1,7 @@ +#pragma once + +double rad2deg(const double &radians); +double deg2rad(const double °rees); +double smoothstep(double edge0, double edge1, double x); +double normalizeAngle(double angle); +double interpolateAngle(double start, double end, double t); \ No newline at end of file diff --git a/src/ErrorHandler.cpp b/src/ErrorHandler.cpp index 4d2577c6..03130a8e 100644 --- a/src/ErrorHandler.cpp +++ b/src/ErrorHandler.cpp @@ -31,6 +31,12 @@ Description: Error Handle (404 or 500) #include "ErrorHandler.hpp" +#ifdef _cpp_fmt_lib +#include +#else +#include +#endif + ErrorHandler::ErrorHandler(const std::shared_ptr &objectMapper) : m_objectMapper(objectMapper) { @@ -46,69 +52,168 @@ ErrorHandler::handleError(const Status &status, const oatpp::String &message, co if (status.code == 404) { - auto response = ResponseFactory::createResponse(Status::CODE_404, R"( + auto response = ResponseFactory::createResponse(Status::CODE_404, fmt::format(R"( - - 404 Not Found - - + color: #e1f0ff; + } + + -
-

Oops! 404

-

抱歉,页面未找到。

-

返回上一页

-
+
+
+

:(

+

+ Lithium Server seemed to have crashed. We're sorry for the + inconvenience. The server will automatically restart in a few minutes. + We're just collecting some error info. +

+

0% complete

+
+
+
+ QR Code +
+
+
+

+ For more information about this issue and possible fixes, visit +
+ + https://github.com/ElementAstro/Lithium/issues + +

+
+ If you call a support person, give them this info:
Stop + Code: {} + Message: {} +
+
+
+
+
+ - )"); + + )", error->code, error->message)); return response; } else diff --git a/src/LithiumApp.cpp b/src/LithiumApp.cpp index cda42aee..5e9ccd94 100644 --- a/src/LithiumApp.cpp +++ b/src/LithiumApp.cpp @@ -42,11 +42,13 @@ Description: Lithium App Enter #include "core/property/iproperty.hpp" #include "plugin/plugin_loader.hpp" #include "script/script_manager.hpp" +#include "atom/plugin/module_loader.hpp" #include "atom/server/global_ptr.hpp" #include "atom/log/loguru.hpp" #include "atom/type/json.hpp" +#include "atom/utils/time.hpp" using json = nlohmann::json; @@ -68,12 +70,14 @@ namespace Lithium m_ThreadManager = GetPtr("ThreadManager"); m_ProcessManager = GetPtr("ProcessManager"); m_MessageBus = GetPtr("MessageBus"); + m_ModuleLoader = GetPtr("ModuleLoader"); m_MessageBus->StartProcessingThread(); m_MessageBus->StartProcessingThread(); m_MessageBus->StartProcessingThread(); m_MessageBus->StartProcessingThread(); m_MessageBus->StartProcessingThread(); + m_MessageBus->StartProcessingThread(); } catch (const std::exception &e) { @@ -92,6 +96,25 @@ namespace Lithium return std::make_shared(); } + void InitLithiumApp() + { + AddPtr("ConfigManager", ConfigManager::createShared()); + AddPtr("MessageBus", MessageBus::createShared()); + AddPtr("ModuleLoader", ModuleLoader::createShared()); + AddPtr("ThreadManager", Thread::ThreadManager::createShared(GetIntConfig("config/server/maxthread"))); + AddPtr("ProcessManager", Process::ProcessManager::createShared(GetIntConfig("config/server/maxprocess"))); + AddPtr("PluginManager", PluginManager::createShared(GetPtr("ProcessManager"))); + AddPtr("TaskManager", std::make_shared("tasks.json")); + AddPtr("TaskGenerator", std::make_shared(GetPtr("DeviceManager"))); + AddPtr("TaskStack", std::make_shared()); + AddPtr("ScriptManager", ScriptManager::createShared(GetPtr("MessageBus"))); + AddPtr("DeviceManager", DeviceManager::createShared(GetPtr("MessageBus"), GetPtr("ConfigManager"))); + } + + // ---------------------------------------------------------------- + // Config + // ---------------------------------------------------------------- + json LithiumApp::GetConfig(const std::string &key_path) const { DLOG_F(INFO, _("Get config value: {}"), key_path); @@ -104,6 +127,10 @@ namespace Lithium m_ConfigManager->setValue(key_path, value); } + // ----------------------------------------------------------------- + // Device + // ----------------------------------------------------------------- + std::vector LithiumApp::getDeviceList(DeviceType type) { return m_DeviceManager->getDeviceList(type); @@ -170,6 +197,10 @@ namespace Lithium return true; } + // ------------------------------------------------------------------ + // Process + // ------------------------------------------------------------------ + bool LithiumApp::createProcess(const std::string &command, const std::string &identifier) { return m_ProcessManager->createProcess(command, identifier); @@ -200,6 +231,10 @@ namespace Lithium return m_ProcessManager->getProcessOutput(identifier); } + // -------------------------------------------------------------------- + // Task + // -------------------------------------------------------------------- + bool LithiumApp::addTask(const std::shared_ptr &task) { return m_TaskManager->addTask(task); @@ -266,6 +301,10 @@ namespace Lithium return true; } + // ----------------------------------------------------------------- + // Thread + // ----------------------------------------------------------------- + /* * Thread Manager Functions Wrapper */ @@ -290,6 +329,10 @@ namespace Lithium return m_ThreadManager->isThreadRunning(name); } + // ----------------------------------------------------------------- + // Chai + // ----------------------------------------------------------------- + bool LithiumApp::runChaiCommand(const std::string &command) { if (m_ScriptManager->runCommand(command)) @@ -365,18 +408,134 @@ namespace Lithium m_ScriptManager->InitMyApp(); } - void InitLithiumApp() + // ----------------------------------------------------------------- + // Module + // ----------------------------------------------------------------- + + bool LithiumApp::loadModule(const std::string &path, const std::string &name) { - AddPtr("ConfigManager", ConfigManager::createShared()); - AddPtr("MessageBus", MessageBus::createShared()); - AddPtr("ThreadManager", Thread::ThreadManager::createShared(GetIntConfig("config/server/maxthread"))); - AddPtr("ProcessManager", Process::ProcessManager::createShared(GetIntConfig("config/server/maxprocess"))); - AddPtr("PluginManager", PluginManager::createShared(GetPtr("ProcessManager"))); - AddPtr("TaskManager", std::make_shared("tasks.json")); - AddPtr("TaskGenerator", std::make_shared(GetPtr("DeviceManager"))); - AddPtr("TaskStack", std::make_shared()); - AddPtr("ScriptManager", ScriptManager::createShared(GetPtr("MessageBus"))); - AddPtr("DeviceManager", DeviceManager::createShared(GetPtr("MessageBus"), GetPtr("ConfigManager"))); + if (m_ModuleLoader->LoadModule(path, name)) + { + return true; + } + else + { + LOG_F(ERROR, _("Failed to load module {} in {}"), name, path); + json res = { + {"command", __func__}, + {"status", false}, + {"message", _(fmt::format("Failed to load module {} in {}", name, path).c_str())}, + {"timestamp", GetTimestampString()}}; + sendJsonMessage("error", res); + return false; + } + } + + bool LithiumApp::unloadModule(const std::string &name) + { + if (m_ModuleLoader->UnloadModule(name)) + { + return true; + } + else + { + LOG_F(ERROR, _("Failed to unload module {}"), name); + json res = { + {"command", __func__}, + {"status", false}, + {"message", _(fmt::format("Failed to unload module {}", name).c_str())}, + {"timestamp", GetTimestampString()}}; + sendJsonMessage("error", res); + return false; + } } + bool LithiumApp::reloadModule(const std::string &name) + { + if (m_ModuleLoader->HasModule(name)) + { + if(unloadModule(name)) + { + return loadModule(m_ModuleLoader->GetModulePath(name), name); + } + } + else + { + LOG_F(ERROR, _("Failed to reload module {}"), name); + json res = { + {"command", __func__}, + {"status", false}, + {"message", _(fmt::format("Failed to reload module {}", name).c_str())}, + {"timestamp", GetTimestampString()}}; + sendJsonMessage("error", res); + return false; + } + } + + bool LithiumApp::reloadAllModules() + { + for (const std::string &name : m_ModuleLoader->GetAllExistedModules()) + { + reloadModule(name); + } + return true; + } + + bool LithiumApp::checkModuleLoaded(const std::string &name) + { + return m_ModuleLoader->HasModule(name); + } + + std::vector LithiumApp::getModuleList() + { + return m_ModuleLoader->GetAllExistedModules(); + } + + bool LithiumApp::enableModule(const std::string &name) + { + if (m_ModuleLoader->EnableModule(name)) + { + return true; + } + else + { + LOG_F(ERROR, _("Failed to enable module {}"), name); + json res = { + {"command", __func__}, + {"status", false}, + {"message", _(fmt::format("Failed to enable module {}", name).c_str())}, + {"timestamp", GetTimestampString()}}; + sendJsonMessage("error", res); + return false; + } + } + + bool LithiumApp::disableModule(const std::string &name) + { + if (m_ModuleLoader->DisableModule(name)) + { + return true; + } + else + { + LOG_F(ERROR, _("Failed to disable module {}"), name); + json res = { + {"command", __func__}, + {"status", false}, + {"message", _(fmt::format("Failed to disable module {}", name).c_str())}, + {"timestamp", GetTimestampString()}}; + sendJsonMessage("error", res); + return false; + } + } + + bool LithiumApp::getModuleStatus(const std::string &name) + { + return m_ModuleLoader->IsModuleEnabled(name); + } + + json LithiumApp::getModuleConfig(const std::string &name) + { + return m_ModuleLoader->GetModuleConfig(name); + } } \ No newline at end of file diff --git a/src/LithiumApp.hpp b/src/LithiumApp.hpp index f4602973..56a4cfc0 100644 --- a/src/LithiumApp.hpp +++ b/src/LithiumApp.hpp @@ -68,6 +68,7 @@ namespace Lithium class ScriptManager; class PluginManager; + class ModuleLoader; class BasicTask; @@ -130,6 +131,18 @@ namespace Lithium bool checkTaskExecutable(const std::string &name); + public: + bool loadModule(const std::string &path, const std::string &name); + bool unloadModule(const std::string &name); + bool reloadModule(const std::string &name); + bool reloadAllModules(); + bool checkModuleLoaded(const std::string &name); + bool enableModule(const std::string &name); + bool disableModule(const std::string &name); + bool getModuleStatus(const std::string &name); + json getModuleConfig(const std::string &name); + std::vector getModuleList(); + public: template void MSSubscribe(const std::string &topic, std::function callback, int priority = 0) @@ -143,6 +156,16 @@ namespace Lithium m_MessageBus->Unsubscribe(topic, callback); } + void sendStringMessage(const std::string &topic, const std::string &message) + { + m_MessageBus->Publish(topic, message); + } + + void sendJsonMessage(const std::string &topic, const json &message) + { + m_MessageBus->Publish(topic, message); + } + public: void addThread(std::function func, const std::string &name); void joinAllThreads(); @@ -168,6 +191,7 @@ namespace Lithium std::shared_ptr m_MessageBus; std::shared_ptr m_PluginManager; std::shared_ptr m_ScriptManager; + std::shared_ptr m_ModuleLoader; }; extern std::shared_ptr MyApp; diff --git a/src/atom/CMakeLists.txt b/src/atom/CMakeLists.txt index 668361f4..8e84d063 100644 --- a/src/atom/CMakeLists.txt +++ b/src/atom/CMakeLists.txt @@ -23,6 +23,8 @@ list(APPEND ${PROJECT_NAME}_SOURCES plugin/module_loader.cpp plugin/compiler.cpp + search/sqlite.cpp + server/serialize.cpp server/deserialize.cpp server/json_checker.cpp @@ -70,6 +72,8 @@ list(APPEND ${PROJECT_NAME}_HEADERS plugin/module_loader.hpp plugin/compiler.hpp + search/sqlite.hpp + server/serialize.hpp server/deserialize.hpp server/json_checker.hpp diff --git a/src/atom/plugin/compiler.cpp b/src/atom/plugin/compiler.cpp index 59e00444..5652aea4 100644 --- a/src/atom/plugin/compiler.cpp +++ b/src/atom/plugin/compiler.cpp @@ -58,7 +58,7 @@ namespace fs = std::filesystem; namespace Lithium { - bool Compiler::CompileToSharedLibrary(const std::string &code, const std::string &moduleName, const std::string &functionName) + bool Compiler::CompileToSharedLibraryAllinOne(const std::string &code, const std::string &moduleName, const std::string &functionName) { DLOG_F(INFO, "Compiling module {}::{}...", moduleName, functionName); @@ -162,35 +162,214 @@ namespace Lithium return false; } - bool Compiler::CopyFile_(const std::string &source, const std::string &destination) + // 编译为共享库 + bool Compiler::CompileToSharedLibrary(const std::string &code, const std::string &moduleName, const std::string &functionName, const std::string &optionsFile) { - std::ifstream src(source, std::ios::binary); - if (!src) + LOG_F(INFO, "Compiling module {}::{}...", moduleName, functionName); + + // 参数校验 + if (!CheckParameters(code, moduleName, functionName)) + { + return false; + } + + // 检查是否已经编译并缓存 + if (IsModuleCached(moduleName, functionName, cache_)) + { + return true; + } + + // 创建输出目录 + const std::string outputDir = "atom/global/"; + if (!CreateOutputDirectory(outputDir)) + { + return false; + } + + // 读取编译选项 + std::string compileOptions = ReadCompileOptions(optionsFile); + if (compileOptions.empty()) + { + return false; + } + + // 检查语法 + if (!SyntaxCheck(code, COMPILER)) { - // spdlog::error("Failed to open file for copy: {}", source); return false; } - std::ofstream dst(destination, std::ios::binary); - if (!dst) + // 指定输出文件路径 + std::string output = outputDir + moduleName + CMD_SUFFIX; + + // 编译代码 + if (!CompileCode(code, COMPILER, compileOptions, output)) + { + return false; + } + + // 缓存已编译的模块 + CacheCompiledModule(moduleName, functionName, output, cache_); + + return true; + } + + // 检查参数是否有效 + bool Compiler::CheckParameters(const std::string &code, const std::string &moduleName, const std::string &functionName) + { + if (code.empty() || moduleName.empty() || functionName.empty()) + { + LOG_F(ERROR, "Invalid parameters."); + return false; + } + return true; + } + + // 检查模块是否已经编译并缓存 + bool Compiler::IsModuleCached(const std::string &moduleName, const std::string &functionName, std::unordered_map &cache_) + { + std::string key = moduleName + "::" + functionName; + auto cachedResult = cache_.find(key); + if (cachedResult != cache_.end()) + { + LOG_F(WARNING, "Module {}::{} is already compiled.", moduleName, functionName); + return true; + } + return false; + } + + // 创建输出目录 + bool Compiler::CreateOutputDirectory(const std::string &outputDir) + { + if (!fs::exists(outputDir)) + { + LOG_F(WARNING, "Output directory does not exist."); + try + { + fs::create_directories(outputDir); + } + catch (const std::exception &e) + { + LOG_F(ERROR, "Failed to create output directory. {}", e.what()); + return false; + } + } + return true; + } + + // 从 JSON 文件中读取编译选项 + std::string Compiler::ReadCompileOptions(const std::string &optionsFile) + { + std::ifstream compileOptionFile(optionsFile); + if (compileOptionFile.is_open()) + { + json compileOptionsJson; + try + { + compileOptionFile >> compileOptionsJson; + if (compileOptionsJson.contains("optimization_level") && compileOptionsJson.contains("cplus_version") && compileOptionsJson.contains("warnings")) + { + std::string compileOptions = compileOptionsJson["optimization_level"].get() + " " + + compileOptionsJson["cplus_version"].get() + " " + + compileOptionsJson["warnings"].get(); + return compileOptions; + } + else + { + LOG_F(ERROR, "Invalid format in compile_options.json."); + return ""; + } + } + catch (const std::exception &e) + { + LOG_F(ERROR, "Error reading compile_options.json: {}", e.what()); + return ""; + } + } + return ""; + } + + // 语法检查 + bool Compiler::SyntaxCheck(const std::string &code, const std::string &compiler) + { + std::stringstream syntaxCheckCmd; + syntaxCheckCmd << compiler << " -fsyntax-only -x c++ -"; + std::string syntaxCheckOutput; + if (RunShellCommand(syntaxCheckCmd.str(), code, syntaxCheckOutput) != 0) { - // spdlog::error("Failed to create file for copy: {}", destination); + LOG_F(ERROR, "Syntax error in C++ code: {}", syntaxCheckOutput); return false; } + return true; + } + + // 编译代码 + bool Compiler::CompileCode(const std::string &code, const std::string &compiler, const std::string &compileOptions, const std::string &output) + { + std::string cmd = compiler + " " + compileOptions + " - " + " -o " + output; + DLOG_F(INFO, "{}", cmd); - dst << src.rdbuf(); + std::string compilationOutput; + int exitCode = RunShellCommand(cmd, code, compilationOutput); + if (exitCode != 0) + { + LOG_F(ERROR, "Failed to compile C++ code: {}", compilationOutput); + return false; + } return true; } + // 缓存已编译的模块 + void Compiler::CacheCompiledModule(const std::string &moduleName, const std::string &functionName, const std::string &output, std::unordered_map &cache_) + { + std::string key = moduleName + "::" + functionName; + cache_[key] = output; + } + + bool Compiler::CopyFile_(const std::string &source, const std::string &destination) + { + try + { + std::ifstream src(source, std::ios::binary); + if (!src) + { + LOG_F(ERROR, "Failed to open file for copy: {}", source); + return false; + } + + std::ofstream dst(destination, std::ios::binary); + if (!dst) + { + LOG_F(ERROR, "Failed to create file for copy: {}", destination); + return false; + } + + dst << src.rdbuf(); + + if (!dst.good()) + { + LOG_F(ERROR, "Error occurred while writing to destination file: {}", destination); + return false; + } + + return true; + } + catch (const std::exception &e) + { + LOG_F(ERROR, "Exception occurred during file copy: {}", e.what()); + return false; + } + } + int Compiler::RunShellCommand(const std::string &command, const std::string &input, std::string &output) { int exitCode = -1; #ifdef _WIN32 - HANDLE hStdoutRead; + HANDLE hStdoutRead = NULL; STARTUPINFO si = {sizeof(si)}; PROCESS_INFORMATION pi; - HANDLE hStdinRead, hStdoutWrite; + HANDLE hStdinRead = NULL, hStdoutWrite = NULL; SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; @@ -203,18 +382,21 @@ namespace Lithium if (!SetHandleInformation(hStdoutWrite, HANDLE_FLAG_INHERIT, 0)) { LOG_F(ERROR, "Failed to set input handle information for shell command: {}", command); + CloseHandle(hStdinRead); return exitCode; } - if (!SetHandleInformation(hStdoutRead, HANDLE_FLAG_INHERIT, 0)) + if (!CreatePipe(&hStdoutRead, &hStdoutWrite, &sa, 0)) { - LOG_F(ERROR, "Failed to set output handle information for shell command: {}", command); + LOG_F(ERROR, "Failed to create output pipe for shell command: {}", command); + CloseHandle(hStdinRead); + CloseHandle(hStdoutWrite); return exitCode; } - si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; - si.wShowWindow = SW_HIDE; + + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.hStdInput = hStdinRead; si.hStdOutput = hStdoutWrite; - si.hStdError = hStdoutWrite; + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); std::vector commandBuffer(command.begin(), command.end()); commandBuffer.push_back('\0'); @@ -225,30 +407,25 @@ namespace Lithium CloseHandle(hStdinRead); CloseHandle(hStdoutWrite); CloseHandle(hStdoutRead); - CloseHandle(hStdoutWrite); return exitCode; } CloseHandle(hStdinRead); CloseHandle(hStdoutWrite); // Read the command output - std::thread outputThread([&]() - { - char buffer[4096]; - DWORD bytesRead; - while (ReadFile(hStdoutRead, buffer, sizeof(buffer), &bytesRead, NULL)) - { - output.append(buffer, bytesRead); - } }); - - // Write the command input - DWORD bytesWritten; - if (!WriteFile(hStdoutWrite, input.c_str(), input.size(), &bytesWritten, NULL)) + char buffer[4096]; + DWORD bytesRead; + while (ReadFile(hStdoutRead, buffer, sizeof(buffer), &bytesRead, NULL)) { - LOG_F(ERROR, "Failed to write input for shell command: {}", command); - return exitCode; + if (bytesRead > 0) + { + output.append(buffer, bytesRead); + } + else + { + break; + } } - CloseHandle(hStdoutWrite); // Wait for the command to finish WaitForSingleObject(pi.hProcess, INFINITE); @@ -258,8 +435,6 @@ namespace Lithium CloseHandle(pi.hProcess); CloseHandle(pi.hThread); CloseHandle(hStdoutRead); - outputThread.join(); - #else FILE *pipe = popen(command.c_str(), "w"); if (!pipe) @@ -276,4 +451,5 @@ namespace Lithium return exitCode; } + } diff --git a/src/atom/plugin/compiler.hpp b/src/atom/plugin/compiler.hpp index 796a520b..7dc6162a 100644 --- a/src/atom/plugin/compiler.hpp +++ b/src/atom/plugin/compiler.hpp @@ -53,9 +53,32 @@ namespace Lithium * \param functionName 入口函数名 * \return 编译是否成功 */ - bool CompileToSharedLibrary(const std::string &code, const std::string &moduleName, const std::string &functionName); + bool CompileToSharedLibrary(const std::string &code, const std::string &moduleName, const std::string &functionName, const std::string &optionsFile = "compile_options.json"); + + bool CompileToSharedLibraryAllinOne(const std::string &code, const std::string &moduleName, const std::string &functionName); private: + // 检查参数是否有效 + bool CheckParameters(const std::string &code, const std::string &moduleName, const std::string &functionName); + // 检查模块是否已经编译并缓存 + bool IsModuleCached(const std::string &moduleName, const std::string &functionName, std::unordered_map &cache_); + + // 创建输出目录 + bool CreateOutputDirectory(const std::string &outputDir); + + // 从 JSON 文件中读取编译选项 + std::string ReadCompileOptions(const std::string &optionsFile); + + // 语法检查 + bool SyntaxCheck(const std::string &code, const std::string &compiler); + // 编译代码 + bool CompileCode(const std::string &code, const std::string &compiler, const std::string &compileOptions, const std::string &output); + + // 缓存已编译的模块 + void CacheCompiledModule(const std::string &moduleName, const std::string &functionName, const std::string &output, std::unordered_map &cache_); + // ---------------------------------------------------- + // 文件、系统操作 + // ---------------------------------------------------- /** * \brief 复制文件 * diff --git a/src/atom/plugin/module_loader.cpp b/src/atom/plugin/module_loader.cpp index e3b0b39c..0609e476 100644 --- a/src/atom/plugin/module_loader.cpp +++ b/src/atom/plugin/module_loader.cpp @@ -49,6 +49,9 @@ namespace fs = std::filesystem; #define PATH_SEPARATOR "/" #endif +#define SET_CONFIG_VALUE(key) \ + config[dir.path().string()][#key] = module_config.value(#key, ""); + namespace Lithium { /** @@ -127,26 +130,38 @@ namespace Lithium if (dir.is_directory()) { // Get the path of the info.json file within the subdirectory - fs::path info_file = dir.path() / "info.json"; + fs::path info_file = dir.path() / "config.json"; // If the info.json exists if (fs::exists(info_file)) { // Append necessary information to the JSON object config[dir.path().string()]["path"] = dir.path().string(); config[dir.path().string()]["config"] = info_file.string(); + DLOG_F(INFO, "Module found: {}, config file: {}", dir.path().string(), info_file.string()); // Read the module configuration from the info.json file and append to the JSON object json module_config = read_config_file(info_file.string()); - config[dir.path().string()]["name"] = module_config["name"]; - config[dir.path().string()]["version"] = module_config["version"]; - config[dir.path().string()]["author"] = module_config["author"]; - config[dir.path().string()]["license"] = module_config.value("license", ""); - config[dir.path().string()]["description"] = module_config.value("description", ""); - // Debug message - DLOG_F(INFO, "Module found: {}, config file: {}", dir.path().string(), info_file.string()); + SET_CONFIG_VALUE(name) + SET_CONFIG_VALUE(version) + SET_CONFIG_VALUE(author) + SET_CONFIG_VALUE(type) + SET_CONFIG_VALUE(dependencies) + SET_CONFIG_VALUE(url) + SET_CONFIG_VALUE(homepage) + SET_CONFIG_VALUE(keywords) + SET_CONFIG_VALUE(repository) + SET_CONFIG_VALUE(bugs) + SET_CONFIG_VALUE(readme) + SET_CONFIG_VALUE(license) + SET_CONFIG_VALUE(description) } } } } + catch (const fs::filesystem_error &e) + { + LOG_F(ERROR, "Failed to iterate modules directory: {}", e.what()); + return {{"error", "Failed to iterate modules directory"}}; + } catch (const std::exception &e) { LOG_F(ERROR, "Failed to iterate modules directory: {}", e.what()); @@ -159,61 +174,7 @@ namespace Lithium return config; } - ModuleLoader::ModuleLoader() - { - m_ThreadManager = std::make_shared(10); - DLOG_F(INFO, "C++ module manager loaded successfully."); - if (m_ThreadManager) - { - m_ThreadManager->addThread([this]() - { if(!LoadOnInit("modules")){ - LOG_F(ERROR,"Failed to load modules on init"); - } }, - "LoadOnInit"); - } - else - { - LOG_F(ERROR, "Failed to initialize thread manager in module loader"); - } - } - - ModuleLoader::ModuleLoader(std::shared_ptr threadManager) - { - m_ThreadManager = threadManager; - DLOG_F(INFO, "C++ module manager loaded successfully."); - if (m_ThreadManager) - { - m_ThreadManager->addThread([this]() - { if(!LoadOnInit("modules")){ - LOG_F(ERROR,"Failed to load modules on init"); - } }, - "LoadOnInit"); - } - else - { - LOG_F(ERROR, "Failed to initialize thread manager in module loader"); - } - } - - ModuleLoader::ModuleLoader(const std::string &dir_name) - { - m_ThreadManager = std::make_shared(10); - DLOG_F(INFO, "C++ module manager loaded successfully."); - if (m_ThreadManager) - { - m_ThreadManager->addThread([this, dir_name]() - { if(!LoadOnInit(dir_name)){ - LOG_F(ERROR,"Failed to load modules on init"); - } }, - "LoadOnInit"); - } - else - { - LOG_F(ERROR, "Failed to initialize thread manager in module loader"); - } - } - - ModuleLoader::ModuleLoader(const std::string &dir_name, std::shared_ptr threadManager) + ModuleLoader::ModuleLoader(const std::string &dir_name = "modules", std::shared_ptr threadManager = Thread::ThreadManager::createShared()) { m_ThreadManager = threadManager; DLOG_F(INFO, "C++ module manager loaded successfully."); @@ -233,31 +194,23 @@ namespace Lithium std::shared_ptr ModuleLoader::createShared() { - return std::make_shared(); + return std::make_shared("modules", Thread::ThreadManager::createShared()); } - std::shared_ptr ModuleLoader::createShared(const std::string &dir_name) - { - return std::make_shared(dir_name); - } - - std::shared_ptr ModuleLoader::createShared(std::shared_ptr threadManager) - { - return std::make_shared(threadManager); - } - std::shared_ptr ModuleLoader::createShared(const std::string &dir_name, std::shared_ptr threadManager) + std::shared_ptr ModuleLoader::createShared(const std::string &dir_name = "modules", std::shared_ptr threadManager = Thread::ThreadManager::createShared()) { return std::make_shared(dir_name, threadManager); } ModuleLoader::~ModuleLoader() { - if (!handles_.empty()) + if (!modules_.empty()) { - for (auto &entry : handles_) + if (!UnloadAllModules()) { - UNLOAD_LIBRARY(entry.second); + LOG_F(ERROR, "Failed to unload all modules"); } + modules_.clear(); } } @@ -298,7 +251,14 @@ namespace Lithium LOG_F(ERROR, "Library {} does not exist", path); return false; } + // Max : The mod's name should be unique, so we check if it already exists + if (HasModule(name)) + { + LOG_F(ERROR, "Module {} already loaded", name); + return false; + } + std::shared_ptr mod = std::make_shared(); // Load the library file void *handle = LOAD_LIBRARY(path.c_str()); if (!handle) @@ -306,22 +266,37 @@ namespace Lithium LOG_F(ERROR, "Failed to load library {}: {}", path, LOAD_ERROR()); return false; } - - // Read the configuration file in JSON format + mod->handle = handle; + // Read the configuration file in JSON format. We will support other formats in the future std::filesystem::path p = path; std::string config_file_path = p.replace_extension(".json").string(); if (std::filesystem::exists(config_file_path)) { json config; - std::ifstream config_file(config_file_path); - config_file >> config; - + try + { + std::ifstream config_file(config_file_path); + config_file >> config; + } + catch (const json::parse_error &e) + { + LOG_F(ERROR, "Failed to parse config file {}: {}", config_file_path, e.what()); + } + mod->config_path = config_file_path; + mod->config_file = p.string(); + mod->config = config; // Check if the required fields exist in the configuration file - if (config.contains("name") && config.contains("version") && config.contains("author")) + if (config.contains("name") && config.contains("version") && config.contains("author") && config.contains("type")) { std::string version = config["version"].get(); std::string author = config["author"].get(); std::string license = config.value("license", ""); + std::string type = config["type"].get(); + + mod->version = version; + mod->author = author; + mod->license = license; + mod->type = type; DLOG_F(INFO, "Loaded Module : {} version {} written by {}{}", config.value("name", "Unknown"), version, author, license.empty() ? "" : (" under " + license)); @@ -337,7 +312,9 @@ namespace Lithium } // Store the library handle in handles_ map with the module name as key - handles_[name] = handle; + // handles_[name] = handle; + modules_[name] = mod; + DLOG_F(INFO, "Loaded module : {}", name); return true; } catch (const std::exception &e) @@ -347,37 +324,25 @@ namespace Lithium } } - bool ModuleLoader::UnloadModule(const std::string &filename) + bool ModuleLoader::UnloadModule(const std::string &name) { try { // Check if the module is loaded and has a valid handle - auto it = handles_.find(filename); - if (it == handles_.end()) + if (!HasModule(name)) { - LOG_F(ERROR, "Module {} is not loaded", filename); + LOG_F(ERROR, "Module {} is not loaded", name); return false; - } - - if (!it->second) - { - LOG_F(ERROR, "Module {}'s handle is null", filename); - return false; - } - + }; // Unload the library and remove its handle from handles_ map - int result = UNLOAD_LIBRARY(it->second); - if (result == 0) + int result = UNLOAD_LIBRARY(GetModule(name)->handle); + if (result != 0) { - DLOG_F(INFO, "Unloaded module : {}", filename); - handles_.erase(it); - return true; - } - else - { - LOG_F(ERROR, "Failed to unload module {}", filename); + LOG_F(ERROR, "Failed to unload module {}", name); return false; } + modules_.erase(name); + return true; } catch (const std::exception &e) { @@ -386,8 +351,24 @@ namespace Lithium } } + bool ModuleLoader::UnloadAllModules() + { + for (auto entry : modules_) + { + int result = UNLOAD_LIBRARY(entry.second->handle); + if (result != 0) + { + LOG_F(ERROR, "Failed to unload module {}", entry.first); + return false; + } + } + modules_.clear(); + return true; + } + bool ModuleLoader::CheckModuleExists(const std::string &name) const { + // Max : Directly check if the library exists seems to be a litle bit slow. May we use filesystem instead? void *handle = LOAD_LIBRARY(name.c_str()); if (handle == nullptr) { @@ -399,34 +380,50 @@ namespace Lithium return true; } - void *ModuleLoader::GetHandle(const std::string &name) const + std::shared_ptr ModuleLoader::GetModule(const std::string &name) const { - auto it = handles_.find(name); - if (it == handles_.end()) + auto it = modules_.find(name); + if (it == modules_.end()) { return nullptr; } return it->second; } + void *ModuleLoader::GetHandle(const std::string &name) const + { + auto it = modules_.find(name); + if (it == modules_.end()) + { + return nullptr; + } + return it->second->handle; + } + bool ModuleLoader::HasModule(const std::string &name) const { - return handles_.count(name) > 0; + return modules_.count(name) > 0; } - bool ModuleLoader::EnableModule(const std::string &module_name) + bool ModuleLoader::EnableModule(const std::string &name) { - auto it = disabled_modules_.find(module_name); - if (it != disabled_modules_.end()) + // Check if the module is loaded + if (!HasModule(name)) { - std::string disabled_file = it->second; + LOG_F(ERROR, "Module {} is not loaded", name); + return false; + } + std::shared_ptr mod = GetModule(name); + if (!mod->enabled.load()) + { + mod->enabled.store(true); + std::string disabled_file = mod->path; std::string enabled_file = disabled_file.substr(0, disabled_file.size() - 8); if (CheckModuleExists(enabled_file)) { if (UnloadModule(enabled_file)) { std::rename(disabled_file.c_str(), enabled_file.c_str()); - disabled_modules_.erase(it); return true; } else @@ -436,47 +433,103 @@ namespace Lithium } else { - LOG_F(ERROR, "Enabled file not found for module {}", module_name); + LOG_F(ERROR, "Enabled file not found for module {}", name); return false; } } return true; } - bool ModuleLoader::DisableModule(const std::string &module_name) + bool ModuleLoader::DisableModule(const std::string &name) { - auto it = handles_.find(module_name); - if (it != handles_.end()) + // Check if the module is loaded + if (!HasModule(name)) + { + LOG_F(ERROR, "Module {} is not loaded", name); + return false; + } + std::shared_ptr mod = GetModule(name); + if (mod->enabled.load()) { - std::string module_path = GetModulePath(module_name); + mod->enabled.store(false); + std::string module_path = GetModulePath(name); if (module_path.empty()) { - LOG_F(ERROR, "Module path not found for module {}", module_name); + LOG_F(ERROR, "Module path not found for module {}", name); return false; } std::string disabled_file = module_path + ".disabled"; if (std::rename(module_path.c_str(), disabled_file.c_str()) == 0) { - handles_.erase(it); - disabled_modules_.insert(std::make_pair(module_name, disabled_file)); + modules_.erase(name); return true; } else { - LOG_F(ERROR, "Failed to disable module {}", module_name); + LOG_F(ERROR, "Failed to disable module {}", name); return false; } } return true; } - std::string ModuleLoader::GetModulePath(const std::string &module_name) + bool ModuleLoader::IsModuleEnabled(const std::string &name) const { - auto it = handles_.find(module_name); - if (it != handles_.end()) + if (!HasModule(name)) + { + LOG_F(ERROR, "Module {} is not loaded", name); + return false; + } + if (GetModule(name)->enabled.load()) + { + return true; + } + return false; + } + + std::string ModuleLoader::GetModuleVersion(const std::string &name) + { + if (HasModule(name)) + { + return GetFunction(name, "GetVersion")(); + } + return ""; + } + + std::string ModuleLoader::GetModuleDescription(const std::string &name) + { + if (HasModule(name)) + { + return GetFunction(name, "GetDescription")(); + } + return ""; + } + + std::string ModuleLoader::GetModuleAuthor(const std::string &name) + { + if (HasModule(name)) + { + return GetFunction(name, "GetAuthor")(); + } + return ""; + } + + std::string ModuleLoader::GetModuleLicense(const std::string &name) + { + if (HasModule(name)) + { + return GetFunction(name, "GetLicense")(); + } + return ""; + } + + std::string ModuleLoader::GetModulePath(const std::string &name) + { + auto it = modules_.find(name); + if (it != modules_.end()) { Dl_info dl_info; - if (dladdr(it->second, &dl_info) != 0) + if (dladdr(it->second->handle, &dl_info) != 0) { return dl_info.dli_fname; } @@ -484,14 +537,23 @@ namespace Lithium return ""; } + json ModuleLoader::GetModuleConfig(const std::string &name) + { + if (HasModule(name)) + { + return GetFunction(name, "GetConfig")(); + } + return {}; + } + const std::vector ModuleLoader::GetAllExistedModules() const { std::vector modules_name; - if (handles_.empty()) + if (modules_.empty()) { return modules_name; } - for (auto module_ : handles_) + for (auto module_ : modules_) { modules_name.push_back(module_.first); } diff --git a/src/atom/plugin/module_loader.hpp b/src/atom/plugin/module_loader.hpp index e0464352..8bd839e9 100644 --- a/src/atom/plugin/module_loader.hpp +++ b/src/atom/plugin/module_loader.hpp @@ -34,6 +34,7 @@ Description: C++ and Modules Loader #include #include #include +#include #if ENABLE_FASTHASH #include "emhash/hash_table8.hpp" #else @@ -95,28 +96,38 @@ namespace Lithium */ json iterator_modules_dir(const std::string &dir_name); - class ModuleLoader + /** + * @brief 用于描述一个模块。 + * + * 用于描述一个模块。注意模组和插件不是一个东西,模组描述的动态库,而插件是从动态库中提取的指针,是可以操作的对象 + */ + class Mod { + // All of the module information public: - /** - * @brief 默认构造函数,创建一个空的 ModuleLoader 对象。 - */ - ModuleLoader(); - - /** - * @brief 使用给定的目录名称参数构造 ModuleLoader 对象。 - * - * @param dir_name 模块所在的目录名称。 - */ - ModuleLoader(const std::string &dir_name); - - /** - * @brief 使用给定的线程管理器参数构造 ModuleLoader 对象。 - * - * @param threadManager 线程管理器的共享指针。 - */ - ModuleLoader(std::shared_ptr threadManager); + int id; + std::string name; + std::string description; + std::string version; + std::string status; + std::string type; + std::string author; + std::string license; + std::string path; + std::string config_path; + std::string config_file; + json config; + + // Module enable status + std::atomic_bool enabled; + + // Module handle pointer + void *handle; + }; + class ModuleLoader + { + public: /** * @brief 使用给定的目录名称和线程管理器参数构造 ModuleLoader 对象。 * @@ -130,29 +141,8 @@ namespace Lithium */ ~ModuleLoader(); - /** - * @brief 创建一个共享的 ModuleLoader 指针对象。 - * - * @return 新创建的共享 ModuleLoader 指针对象。 - */ static std::shared_ptr createShared(); - /** - * @brief 使用给定的目录名称参数创建一个共享的 ModuleLoader 指针对象。 - * - * @param dir_name 模块所在的目录名称。 - * @return 新创建的共享 ModuleLoader 指针对象。 - */ - static std::shared_ptr createShared(const std::string &dir_name); - - /** - * @brief 使用给定的线程管理器参数创建一个共享的 ModuleLoader 指针对象。 - * - * @param threadManager 线程管理器的共享指针。 - * @return 新创建的共享 ModuleLoader 指针对象。 - */ - static std::shared_ptr createShared(std::shared_ptr threadManager); - /** * @brief 使用给定的目录名称和线程管理器参数创建一个共享的 ModuleLoader 指针对象。 * @@ -191,43 +181,81 @@ namespace Lithium */ bool UnloadModule(const std::string &name); + /** + * @brief 卸载所有动态库 + * + * @return true 所有动态库卸载成功 + * @return false 所有动态库卸载失败 + */ + bool UnloadAllModules(); + + /** + * @brief 判断指定名称的模块是否存在 + * + * @param name 模块名称 + * @return true 模块存在 + * @return false 模块不存在 + */ bool HasModule(const std::string &name) const; + /** + * @brief 获取指定名称的模块 + * + * @param name 模块名称 + * @return std::shared_ptr 模块指针 + */ + std::shared_ptr GetModule(const std::string &name) const; + + /** + * @brief 检查指定名称的模块是否存在 + * + * @param name 模块名称 + * @return true 模块存在 + * @return false 模块不存在 + */ bool CheckModuleExists(const std::string &name) const; /** * @brief 允许指定模块 * - * @param module_name 模块名称 + * @param name 模块名称 * @return true 成功允许模块 * @return false 允许模块失败 */ - bool EnableModule(const std::string &module_name); + bool EnableModule(const std::string &name); /** * @brief 禁用指定模块 * - * @param module_name 模块名称 + * @param name 模块名称 * @return true 成功禁用模块 * @return false 禁用模块失败 */ - bool DisableModule(const std::string &module_name); + bool DisableModule(const std::string &name); + + /* + * @brief 判断指定模块是否被允许 + * @param name 模块名称 + * @return true 指定模块被允许 + * @return false 指定模块未被允许 + */ + bool IsModuleEnabled(const std::string &name) const; /** * @brief 获取指定模块中的函数指针 * * @tparam T 函数指针类型 - * @param module_name 模块名称 + * @param name 模块名称 * @param function_name 函数名称 * @return T 返回函数指针,如果获取失败则返回nullptr */ template - T GetFunction(const std::string &module_name, const std::string &function_name) + T GetFunction(const std::string &name, const std::string &function_name) { - auto handle_it = handles_.find(module_name); + auto handle_it = handles_.find(name); if (handle_it == handles_.end()) { - LOG_F(ERROR, "Failed to find module %s", module_name.c_str()); + LOG_F(ERROR, "Failed to find module %s", name.c_str()); return nullptr; } @@ -235,7 +263,7 @@ namespace Lithium if (!func_ptr) { - LOG_F(ERROR, "Failed to get symbol %s from module %s: %s", function_name.c_str(), module_name.c_str(), dlerror()); + LOG_F(ERROR, "Failed to get symbol %s from module %s: %s", function_name.c_str(), name.c_str(), dlerror()); return nullptr; } @@ -246,26 +274,26 @@ namespace Lithium * @brief 从指定模块中获取实例对象 * * @tparam T 实例对象类型 - * @param module_name 模块名称 + * @param name 模块名称 * @param config 实例对象的配置参数 * @param symbol_name 获取实例对象的符号名称 * @return std::shared_ptr 返回实例对象的智能指针,如果获取失败则返回nullptr */ template - std::shared_ptr GetInstance(const std::string &module_name, const json &config, + std::shared_ptr GetInstance(const std::string &name, const json &config, const std::string &symbol_name) { - auto handle_it = handles_.find(module_name); + auto handle_it = handles_.find(name); if (handle_it == handles_.end()) { - LOG_F(ERROR, "Failed to find module %s", module_name.c_str()); + LOG_F(ERROR, "Failed to find module %s", name.c_str()); return nullptr; } - auto get_instance_func = GetFunction (*)(const json &)>(module_name, symbol_name); + auto get_instance_func = GetFunction (*)(const json &)>(name, symbol_name); if (!get_instance_func) { - LOG_F(ERROR, "Failed to get symbol %s from module %s: %s", symbol_name.c_str(), module_name.c_str(), dlerror()); + LOG_F(ERROR, "Failed to get symbol %s from module %s: %s", symbol_name.c_str(), name.c_str(), dlerror()); return nullptr; } @@ -276,18 +304,18 @@ namespace Lithium * @brief 获取指定模块的任务实例指针 * * @tparam T 任务类型 - * @param module_name 模块名称 + * @param name 模块名称 * @param config 配置信息 * @param instance_function_name 实例化函数名称 * @return std::shared_ptr 任务实例指针 - * std::shared_ptr task = GetInstancePointer(module_name, config, "GetTaskInstance"); - * std::shared_ptr device = GetInstancePointer(module_name, config, "GetDeviceInstance"); - * std::shared_ptr plugin = GetInstancePointer(module_name, config, "GetPluginInstance"); + * std::shared_ptr task = GetInstancePointer(name, config, "GetTaskInstance"); + * std::shared_ptr device = GetInstancePointer(name, config, "GetDeviceInstance"); + * std::shared_ptr plugin = GetInstancePointer(name, config, "GetPluginInstance"); */ template - std::shared_ptr GetInstancePointer(const std::string &module_name, const json &config, const std::string &instance_function_name) + std::shared_ptr GetInstancePointer(const std::string &name, const json &config, const std::string &instance_function_name) { - return GetInstance(module_name, config, instance_function_name); + return GetInstance(name, config, instance_function_name); } public: @@ -296,16 +324,57 @@ namespace Lithium * * @param name 句柄名称。 * @return 对应名称的句柄指针,如果未找到则返回空指针。 + * @note 该函数不检查模块是否被允许。这个函数的使用其实是很危险的,不建议暴露到模组或者脚本中被随意调用。 */ void *GetHandle(const std::string &name) const; + // --------------------------------------------------------------------- + // Get Module Info + // --------------------------------------------------------------------- + + /** + * @brief 获取指定模块的版本号 + * @param name 模块名称 + * @return 模块版本号 + */ + std::string GetModuleVersion(const std::string &name); + + /** + * @brief 获取指定模块的描述信息 + * @param name 模块名称 + * @return 模块描述信息 + */ + std::string GetModuleDescription(const std::string &name); + + /** + * @brief 获取指定模块的作者信息 + * @param name 模块名称 + * @return 模块作者信息 + */ + std::string GetModuleAuthor(const std::string &name); + + /** + * @brief 获取指定模块的许可证信息 + * @param name 模块名称 + * @return 模块许可证信息 + */ + std::string GetModuleLicense(const std::string &name); + /** * @brief 获取给定模块名称的模块路径。 * - * @param module_name 模块名称。 + * @param name 模块名称。 * @return 对应模块名称的模块路径。 */ - std::string GetModulePath(const std::string &module_name); + std::string GetModulePath(const std::string &name); + + /** + * @brief 获取给定模块名称的模块配置。 + * + * @param name 模块名称。 + * @return 对应模块名称的模块配置。 + */ + json GetModuleConfig(const std::string &name); /** * @brief 获取所有存在的模块名称。 @@ -316,13 +385,11 @@ namespace Lithium private: #if ENABLE_FASTHASH - emhash8::HashMap handles_; - emhash8::HashMap disabled_modules_; + emhash8::HashMap> modules_; // 模块哈希表 #else - std::unordered_map handles_; - std::unordered_map disabled_modules_; + std::unordered_map> modules_; // 模块哈希表 #endif - + // Injected Thread Manager std::shared_ptr m_ThreadManager; }; } \ No newline at end of file diff --git a/src/atom/search/mysql.cpp b/src/atom/search/mysql.cpp new file mode 100644 index 00000000..1df0fedf --- /dev/null +++ b/src/atom/search/mysql.cpp @@ -0,0 +1,334 @@ +/* + * mysql.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-6 + +Description: Simple Mysql wrapper + +**************************************************/ + +#include "mysql.hpp" +#include + +#include "atom/log/loguru.hpp" + +MysqlDB::MysqlDB(const char *host, const char *user, const char *password, const char *database) +{ + db = mysql_init(nullptr); + if (db == nullptr) + { + handleMySQLError(); + return; + } + + if (!mysql_real_connect(db, host, user, password, database, 0, nullptr, 0)) + { + handleMySQLError(); + } +} + +MysqlDB::~MysqlDB() +{ + if (db != nullptr) + { + mysql_close(db); + } +} + +bool MysqlDB::executeQuery(const char *query) +{ + if (mysql_query(db, query) != 0) + { + handleMySQLError(); + return false; + } + + return true; +} + +void MysqlDB::selectData(const char *query) +{ + if (!executeQuery(query)) + { + return; + } + + MYSQL_RES *result = mysql_store_result(db); + if (result == nullptr) + { + handleMySQLError(); + return; + } + + int num_fields = mysql_num_fields(result); + + MYSQL_ROW row; + while ((row = mysql_fetch_row(result))) + { + for (int i = 0; i < num_fields; i++) + { + std::cout << row[i] << " "; + } + std::cout << std::endl; + } + + mysql_free_result(result); +} + +int MysqlDB::getIntValue(const char *query) +{ + if (!executeQuery(query)) + { + return 0; + } + + MYSQL_RES *result = mysql_store_result(db); + if (result == nullptr) + { + handleMySQLError(); + return 0; + } + + MYSQL_ROW row = mysql_fetch_row(result); + + int value = 0; + if (row != nullptr) + { + value = atoi(row[0]); + } + + mysql_free_result(result); + + return value; +} + +double MysqlDB::getDoubleValue(const char *query) +{ + if (!executeQuery(query)) + { + return 0.0; + } + + MYSQL_RES *result = mysql_store_result(db); + if (result == nullptr) + { + handleMySQLError(); + return 0.0; + } + + MYSQL_ROW row = mysql_fetch_row(result); + + double value = 0.0; + if (row != nullptr) + { + value = atof(row[0]); + } + + mysql_free_result(result); + + return value; +} + +const char *MysqlDB::getTextValue(const char *query) +{ + if (!executeQuery(query)) + { + return ""; + } + + MYSQL_RES *result = mysql_store_result(db); + if (result == nullptr) + { + handleMySQLError(); + return ""; + } + + MYSQL_ROW row = mysql_fetch_row(result); + + const char *value = ""; + if (row != nullptr) + { + value = row[0]; + } + + mysql_free_result(result); + + return value; +} + +bool MysqlDB::searchData(const char *query, const char *searchTerm) +{ + if (!executeQuery(query)) + { + return false; + } + + MYSQL_RES *result = mysql_store_result(db); + if (result == nullptr) + { + handleMySQLError(); + return false; + } + + int num_fields = mysql_num_fields(result); + + MYSQL_ROW row; + while ((row = mysql_fetch_row(result))) + { + for (int i = 0; i < num_fields; i++) + { + if (strcmp(row[i], searchTerm) == 0) + { + mysql_free_result(result); + return true; + } + } + } + + mysql_free_result(result); + + return false; +} + +bool MysqlDB::updateData(const char *query) +{ + return executeQuery(query); +} + +bool MysqlDB::deleteData(const char *query) +{ + return executeQuery(query); +} + +bool MysqlDB::beginTransaction() +{ + return executeQuery("START TRANSACTION"); +} + +bool MysqlDB::commitTransaction() +{ + return executeQuery("COMMIT"); +} + +bool MysqlDB::rollbackTransaction() +{ + return executeQuery("ROLLBACK"); +} + +void MysqlDB::handleMySQLError() +{ + if (db == nullptr) + { + return; + } + + const char *error_msg = mysql_error(db); + if (error_msg != nullptr && strlen(error_msg) > 0) + { + LOG_F(ERROR, "MySQL error: {}", error_msg); + if (errorCallback != nullptr) + { + errorCallback(error_msg); + } + } +} + +bool MysqlDB::validateData(const char *query, const char *validationQuery) +{ + if (!executeQuery(query)) + { + return false; + } + + MYSQL_RES *result = mysql_store_result(db); + if (result == nullptr) + { + handleMySQLError(); + return false; + } + + int num_fields = mysql_num_fields(result); + + MYSQL_ROW row; + while ((row = mysql_fetch_row(result))) + { + if (!executeQuery(validationQuery)) + { + mysql_free_result(result); + return false; + } + + MYSQL_RES *validationResult = mysql_store_result(db); + if (validationResult == nullptr) + { + handleMySQLError(); + mysql_free_result(result); + return false; + } + + MYSQL_ROW validationRow = mysql_fetch_row(validationResult); + if (validationRow == nullptr) + { + mysql_free_result(validationResult); + mysql_free_result(result); + return false; + } + + bool isValid = true; + for (int i = 0; i < num_fields; i++) + { + if (strcmp(row[i], validationRow[i]) != 0) + { + isValid = false; + break; + } + } + + mysql_free_result(validationResult); + + if (isValid) + { + mysql_free_result(result); + return true; + } + } + + mysql_free_result(result); + + return false; +} + +void MysqlDB::selectDataWithPagination(const char *query, int limit, int offset) +{ + char sql[1024]; + snprintf(sql, sizeof(sql), "%s LIMIT %d OFFSET %d", query, limit, offset); + + selectData(sql); +} + +void MysqlDB::setErrorMessageCallback(const std::function &errorCallback) +{ + this->errorCallback = errorCallback; +} diff --git a/src/atom/search/mysql.hpp b/src/atom/search/mysql.hpp new file mode 100644 index 00000000..b9b2fbf7 --- /dev/null +++ b/src/atom/search/mysql.hpp @@ -0,0 +1,106 @@ +/* + * mysql.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-6 + +Description: Simple Mysql wrapper + +**************************************************/ + +#pragma once + +#include +#include + +class MysqlDB +{ +public: + MysqlDB(const char *host, const char *user, const char *password, const char *database); + ~MysqlDB(); + + bool executeQuery(const char *query); + void selectData(const char *query); + int getIntValue(const char *query); + double getDoubleValue(const char *query); + const char *getTextValue(const char *query); + bool searchData(const char *query, const char *searchTerm); + bool updateData(const char *query); + bool deleteData(const char *query); + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); + void handleMySQLError(); + bool validateData(const char *query, const char *validationQuery); + void selectDataWithPagination(const char *query, int limit, int offset); + void setErrorMessageCallback(const std::function &errorCallback); + +private: + MYSQL *db; + +private: + std::function errorCallback = [](const char *errorMessage) + { + }; +}; + +/* +int main() +{ + const char *host = "localhost"; + const char *user = "root"; + const char *password = "password"; + const char *database = "mydatabase"; + + MysqlDB db(host, user, password, database); + + // 执行查询语句 + db.selectData("SELECT * FROM customers"); + + // 执行更新语句 + bool updateResult = db.updateData("UPDATE customers SET address='New Address' WHERE id=1"); + if (updateResult) + { + std::cout << "Update successful" << std::endl; + } + else + { + std::cout << "Update failed" << std::endl; + } + + // 获取整数值 + int intValue = db.getIntValue("SELECT COUNT(*) FROM customers"); + std::cout << "Total customers: " << intValue << std::endl; + + // 获取浮点数值 + double doubleValue = db.getDoubleValue("SELECT AVG(price) FROM products"); + std::cout << "Average price: " << doubleValue << std::endl; + + // 获取文本值 + const char *textValue = db.getTextValue("SELECT name FROM customers WHERE id=1"); + std::cout << "Customer name: " << textValue << std::endl; + + return 0; +} +*/ \ No newline at end of file diff --git a/src/atom/search/sqlite.cpp b/src/atom/search/sqlite.cpp new file mode 100644 index 00000000..521a08aa --- /dev/null +++ b/src/atom/search/sqlite.cpp @@ -0,0 +1,248 @@ +/* + * sqlite.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-6 + +Description: Simple Sqilte3 wrapper + +**************************************************/ + +#include "sqlite.hpp" + +#include "atom/log/loguru.hpp" + +SqliteDB::SqliteDB(const char *dbPath) +{ + errorCallback = [](const char *errorMessage) + { + LOG_F(ERROR, "{}", errorMessage); + }; + int rc = sqlite3_open(dbPath, &db); + if (rc) + { + errorCallback(sqlite3_errmsg(db)); + } + else + { + DLOG_F(INFO, "Open database: {}", dbPath); + } +} + +SqliteDB::~SqliteDB() +{ + sqlite3_close(db); + DLOG_F(INFO, "Close database"); +} + +bool SqliteDB::executeQuery(const char *query) +{ + char *errorMessage = 0; + int rc = sqlite3_exec(db, query, 0, 0, &errorMessage); + if (rc != SQLITE_OK) + { + errorCallback(sqlite3_errmsg(db)); + sqlite3_free(errorMessage); + return false; + } + return true; +} + +void SqliteDB::selectData(const char *query) +{ + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, query, -1, &stmt, 0); + + if (rc == SQLITE_OK) + { + while (sqlite3_step(stmt) == SQLITE_ROW) + { + // 处理查询结果 + } + } + else + { + errorCallback(sqlite3_errmsg(db)); + } + + sqlite3_finalize(stmt); +} + +int SqliteDB::getIntValue(const char *query) +{ + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, query, -1, &stmt, 0); + int result = 0; + + if (rc == SQLITE_OK) + { + if (sqlite3_step(stmt) == SQLITE_ROW) + { + result = sqlite3_column_int(stmt, 0); + } + } + else + { + errorCallback(sqlite3_errmsg(db)); + } + + sqlite3_finalize(stmt); + + return result; +} + +double SqliteDB::getDoubleValue(const char *query) +{ + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, query, -1, &stmt, 0); + double result = 0.0; + + if (rc == SQLITE_OK) + { + if (sqlite3_step(stmt) == SQLITE_ROW) + { + result = sqlite3_column_double(stmt, 0); + } + } + else + { + errorCallback(sqlite3_errmsg(db)); + } + + sqlite3_finalize(stmt); + + return result; +} + +const unsigned char *SqliteDB::getTextValue(const char *query) +{ + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, query, -1, &stmt, 0); + const unsigned char *result; + + if (rc == SQLITE_OK) + { + if (sqlite3_step(stmt) == SQLITE_ROW) + { + result = sqlite3_column_text(stmt, 0); + } + } + else + { + errorCallback(sqlite3_errmsg(db)); + } + + sqlite3_finalize(stmt); + + return result; +} + +bool SqliteDB::searchData(const char *query, const char *searchTerm) +{ + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, query, -1, &stmt, 0); + bool result = false; + + if (rc == SQLITE_OK) + { + sqlite3_bind_text(stmt, 1, searchTerm, -1, SQLITE_TRANSIENT); + + if (sqlite3_step(stmt) == SQLITE_ROW) + { + result = true; + } + } + else + { + errorCallback(sqlite3_errmsg(db)); + } + + sqlite3_finalize(stmt); + + return result; +} + +bool SqliteDB::updateData(const char *query) +{ + return executeQuery(query); +} + +bool SqliteDB::deleteData(const char *query) +{ + return executeQuery(query); +} + +bool SqliteDB::beginTransaction() +{ + return executeQuery("BEGIN TRANSACTION"); +} + +bool SqliteDB::commitTransaction() +{ + return executeQuery("COMMIT TRANSACTION"); +} + +bool SqliteDB::rollbackTransaction() +{ + return executeQuery("ROLLBACK TRANSACTION"); +} + +void SqliteDB::handleSQLError() +{ + errorCallback(sqlite3_errmsg(db)); +} + +bool SqliteDB::validateData(const char *query, const char *validationQuery) +{ + if (!executeQuery(query)) + { + return false; + } + + int validationResult = getIntValue(validationQuery); + + if (validationResult == 1) + { + return true; + } + else + { + return false; + } +} + +void SqliteDB::selectDataWithPagination(const char *query, int limit, int offset) +{ + // 构建带有分页的查询语句 + std::string queryWithPagination = query; + queryWithPagination += " LIMIT " + std::to_string(limit) + " OFFSET " + std::to_string(offset); + + // 执行分页查询 + selectData(queryWithPagination.c_str()); +} + +void SqliteDB::setErrorMessageCallback(const std::function &errorCallback) +{ + this->errorCallback = errorCallback; +} \ No newline at end of file diff --git a/src/atom/search/sqlite.hpp b/src/atom/search/sqlite.hpp new file mode 100644 index 00000000..d781cdca --- /dev/null +++ b/src/atom/search/sqlite.hpp @@ -0,0 +1,218 @@ +/* + * sqlite.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-6 + +Description: Simple Sqilte3 wrapper + +**************************************************/ + +#pragma once + +#include +#include +#include + +/** + * @class SqliteDB + * @brief 表示一个用于操作SQLite数据库的类 + */ +class SqliteDB +{ +private: + sqlite3 *db; /**< SQLite数据库连接对象 */ + +public: + /** + * @brief 构造函数 + * @param dbPath 数据库文件的路径 + */ + SqliteDB(const char *dbPath); + + /** + * @brief 析构函数 + */ + ~SqliteDB(); + + /** + * @brief 执行查询语句 + * @param query 查询语句 + * @return 执行是否成功 + */ + bool executeQuery(const char *query); + + /** + * @brief 查询并获取数据 + * @param query 查询语句 + */ + void selectData(const char *query); + + /** + * @brief 获取整型值 + * @param query 查询语句 + * @return 整型值 + */ + int getIntValue(const char *query); + + /** + * @brief 获取浮点型值 + * @param query 查询语句 + * @return 浮点型值 + */ + double getDoubleValue(const char *query); + + /** + * @brief 获取文本值 + * @param query 查询语句 + * @return 文本值 + */ + const unsigned char *getTextValue(const char *query); + + /** + * @brief 在查询结果中搜索指定的项 + * @param query 查询语句 + * @param searchTerm 要搜索的项 + * @return 是否找到匹配项 + */ + bool searchData(const char *query, const char *searchTerm); + + /** + * @brief 更新数据 + * @param query 更新语句 + * @return 更新是否成功 + */ + bool updateData(const char *query); + + /** + * @brief 删除数据 + * @param query 删除语句 + * @return 删除是否成功 + */ + bool deleteData(const char *query); + + /** + * @brief 开始数据库事务 + * @return 是否成功开始事务 + */ + bool beginTransaction(); + + /** + * @brief 提交数据库事务 + * @return 是否成功提交事务 + */ + bool commitTransaction(); + + /** + * @brief 回滚数据库事务 + * @return 是否成功回滚事务 + */ + bool rollbackTransaction(); + + /** + * @brief 处理SQLite错误 + */ + void handleSQLError(); + + /** + * @brief 验证数据是否符合指定的查询条件 + * @param query 查询语句 + * @param validationQuery 验证条件语句 + * @return 验证结果 + */ + bool validateData(const char *query, const char *validationQuery); + + /** + * @brief 分页查询并获取数据 + * @param query 查询语句 + * @param limit 每页记录数 + * @param offset 偏移量 + */ + void selectDataWithPagination(const char *query, int limit, int offset); + + /** + * @brief 设置错误消息回调函数 + * @param errorCallback 错误消息回调函数 + */ + void setErrorMessageCallback(const std::function &errorCallback); + +private: + std::function errorCallback = [](const char *errorMessage) + { + }; +}; + +/* +int main() +{ + // 创建数据库对象,并指定数据库文件路径 + SqliteDB db("example.db"); + + db.setErrorMessageCallback([](const char *errorMessage) + { + std::cerr << "Error: " << errorMessage << std::endl; + // 进行其他处理,如记录日志等 + }); + + // 执行创建表的 SQL 语句 + const char *createTableQuery = "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)"; + db.executeQuery(createTableQuery); + + // 执行插入数据的 SQL 语句 + const char *insertQuery = "INSERT INTO users (name, age) VALUES ('Alice', 25)"; + db.executeQuery(insertQuery); + + // 执行查询数据的 SQL 语句 + const char *selectQuery = "SELECT * FROM users"; + db.selectData(selectQuery); + + bool exists = db.searchData("SELECT * FROM users WHERE name = 'Alice'", "Alice"); + if (exists) + { + std::cout << "User Alice exists" << std::endl; + } + else + { + std::cout << "User Alice does not exist" << std::endl; + } + + // 获取整数、浮点数和文本值 + int userId = db.getIntValue("SELECT id FROM users WHERE name = 'Alice'"); + double userAge = db.getDoubleValue("SELECT age FROM users WHERE name = 'Alice'"); + const unsigned char *userName = db.getTextValue("SELECT name FROM users WHERE id = 1"); + std::cout << "User Age: " << userAge << std::endl; + std::cout << "User ID: " << userId << std::endl; + std::cout << "User Name: " << userName << std::endl; + + // 更新数据 + const char *updateQuery = "UPDATE users SET age = 26 WHERE name = 'Alice'"; + db.updateData(updateQuery); + + // 删除数据 + const char *deleteQuery = "DELETE FROM users WHERE name = 'Alice'"; + db.deleteData(deleteQuery); + + return 0; +} +*/ diff --git a/src/atom/server/search.cpp b/src/atom/server/search.cpp new file mode 100644 index 00000000..9589025e --- /dev/null +++ b/src/atom/server/search.cpp @@ -0,0 +1,337 @@ +/* + * search.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-4 + +Description: A search engine + +**************************************************/ + +#include "search.hpp" + +std::vector FuzzyMatch::match(const std::string &query, const std::unordered_map> &index, int threshold = 3) +{ + std::vector results; + for (const auto &[hashVal, strList] : index) + { + try + { + for (const std::string &str : strList) + { + if (editDistance(query, str) < threshold) + { + results.push_back(str); + } + } + } + catch (const std::invalid_argument &) + { + // 忽略空字符串列表 + } + } + return results; +} + +int FuzzyMatch::editDistance(const std::string &s1, const std::string &s2) +{ + int n1 = s1.size(), n2 = s2.size(); + std::vector> dp(n1 + 1, std::vector(n2 + 1)); + for (int i = 0; i <= n1; ++i) + dp[i][0] = i; + for (int j = 0; j <= n2; ++j) + dp[0][j] = j; + for (int i = 1; i <= n1; ++i) + { + for (int j = 1; j <= n2; ++j) + { + if (s1[i - 1] == s2[j - 1]) + { + dp[i][j] = dp[i - 1][j - 1]; + } + else + { + dp[i][j] = std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}) + 1; + } + } + } + return dp[n1][n2]; +} + +std::vector RegexMatch::match(const std::string &query, const std::unordered_map> &index, int /*threshold*/ = 0) +{ + std::vector results; + try + { + std::regex regexPattern(query); + for (const auto &[_, strList] : index) + { + for (const std::string &str : strList) + { + if (std::regex_search(str, regexPattern)) + { + results.push_back(str); + } + } + } + } + catch (const std::regex_error &) + { + // 忽略无效的正则表达式 + } + return results; +} + +HammingMatch::HammingMatch(int maxDistance) : maxDistance_(maxDistance) {} + +std::vector HammingMatch::match(const std::string &query, const std::unordered_map> &index, int /*threshold*/ = 0) +{ + std::vector results; + for (const auto &[hashVal, strList] : index) + { + try + { + for (const std::string &str : strList) + { + if (hammingDistance(query, str) <= maxDistance_) + { + results.push_back(str); + } + } + } + catch (const std::invalid_argument &) + { + // 忽略空字符串列表 + } + } + return results; +} + +int HammingMatch::hammingDistance(const std::string &s1, const std::string &s2) +{ + if (s1.size() != s2.size()) + { + throw std::invalid_argument("strings have different lengths"); + } + int distance = 0; + for (size_t i = 0; i < s1.size(); i++) + { + if (s1[i] != s2[i]) + { + distance++; + } + } + return distance; +} + +TfIdfMatch::TfIdfMatch(const std::vector &data) +{ + buildIndex(data); + buildIdf(); +} + +std::vector TfIdfMatch::match(const std::string &query, const std::unordered_map> &index, int /*threshold*/ = 0) +{ + std::vector results; + std::unordered_map queryTf = calculateTf(query); + std::unordered_map queryTfidf = calculateTfidf(queryTf); + for (const auto &[_, strList] : index) + { + std::unordered_map strTfidf = calculateTfidf(strList); + double similarity = cosineSimilarity(queryTfidf, strTfidf); + if (similarity > 0.0) + { + results.push_back(strList[0]); + } + } + return results; +} + +void TfIdfMatch::buildIndex(const std::vector &data) +{ + for (const auto &str : data) + { + std::unordered_map tf = calculateTf(str); + termFrequency_.push_back(tf); + } +} + +void TfIdfMatch::buildIdf() +{ + size_t numDocs = termFrequency_.size(); + for (const auto &tf : termFrequency_) + { + for (const auto &[term, _] : tf) + { + inverseDocumentFrequency_[term] += 1.0; + } + } + for (auto &[_, idf] : inverseDocumentFrequency_) + { + if (idf > 0.0) + { + idf = std::log(numDocs / idf); + } + else + { + idf = 0.0; // 或者设置为一个较小的非零值 + } + } +} + +std::unordered_map TfIdfMatch::calculateTf(const std::string &str) +{ + std::unordered_map tf; + for (const char &c : str) + { + tf[std::string(1, c)]++; + } + return tf; +} + +std::unordered_map TfIdfMatch::calculateTf(const std::vector &strList) +{ + std::unordered_map tf; + for (const auto &str : strList) + { + for (const char &c : str) + { + tf[std::string(1, c)]++; + } + } + size_t numDocs = strList.size(); + for (auto &[_, val] : tf) + { + val /= numDocs; + } + return tf; +} + +std::unordered_map TfIdfMatch::calculateTfidf(const std::unordered_map &tf) +{ + std::unordered_map tfidf; + for (const auto &[term, tfVal] : tf) + { + if (inverseDocumentFrequency_.count(term)) + { + tfidf[term] = tfVal * inverseDocumentFrequency_[term]; + } + } + return tfidf; +} + +std::unordered_map TfIdfMatch::calculateTfidf(const std::vector &strList) +{ + std::unordered_map tf = calculateTf(strList); + std::unordered_map tfidf = calculateTfidf(tf); + double sum = 0.0; + for (const auto &[_, val] : tfidf) + { + sum += val * val; + } + double norm = std::sqrt(sum); + for (auto &[term, val] : tfidf) + { + val /= norm; + } + return tfidf; +} + +double TfIdfMatch::cosineSimilarity(const std::unordered_map &tfidf1, const std::unordered_map &tfidf2) +{ + std::vector commonTerms; + for (const auto &[term, _] : tfidf1) + { + if (tfidf2.count(term)) + { + commonTerms.push_back(term); + } + } + double dotProduct = 0.0; + double norm1 = 0.0; + double norm2 = 0.0; + for (const auto &term : commonTerms) + { + double val1 = tfidf1.at(term); + double val2 = tfidf2.at(term); + dotProduct += val1 * val2; + norm1 += val1 * val1; + norm2 += val2 * val2; + } + norm1 = std::sqrt(norm1); + norm2 = std::sqrt(norm2); + if (norm1 == 0.0 || norm2 == 0.0) + { + return 0.0; + } + else + { + return dotProduct / (norm1 * norm2); + } +} + +SearchEngine::SearchEngine(const std::vector &data, MatchStrategy *strategy) : strategy_(strategy) +{ + buildIndex(data); +} + +void SearchEngine::setMatchStrategy(MatchStrategy *strategy) +{ + strategy_ = strategy; +} + +std::vector SearchEngine::search(const std::string &query, int threshold = 3) +{ + return strategy_->match(query, index_, threshold); +} + +void SearchEngine::buildIndex(const std::vector &data) +{ + for (const auto &str : data) + { + size_t hashVal = std::hash{}(str); + index_[hashVal].push_back(str); + } +} + +void SearchEngine::addData(const std::string &str) +{ + size_t hashVal = std::hash{}(str); + index_[hashVal].push_back(str); +} + +void SearchEngine::removeData(const std::string &str) +{ + size_t hashVal = std::hash{}(str); + auto it = index_.find(hashVal); + if (it != index_.end()) + { + auto &strList = it->second; + strList.erase(std::remove(strList.begin(), strList.end(), str), strList.end()); + if (strList.empty()) + { + index_.erase(it); + } + } +} diff --git a/src/atom/server/search.hpp b/src/atom/server/search.hpp new file mode 100644 index 00000000..7eef4387 --- /dev/null +++ b/src/atom/server/search.hpp @@ -0,0 +1,304 @@ +/* + * search.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-4 + +Description: A search engine + +**************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief Abstract base class for matching strategies. + */ +class MatchStrategy +{ +public: + /** + * @brief Matches the given query against the index using a specific strategy. + * @param query The query string to match. + * @param index The index containing the data to match against. + * @param threshold The matching threshold (optional). + * @return A vector of matched strings. + */ + virtual std::vector match(const std::string &query, const std::unordered_map> &index, int threshold = 3) = 0; +}; + +/** + * @brief Fuzzy matching strategy based on edit distance. + */ +class FuzzyMatch : public MatchStrategy +{ +public: + /** + * @brief Matches the given query against the index using fuzzy matching. + * @param query The query string to match. + * @param index The index containing the data to match against. + * @param threshold The matching threshold. + * @return A vector of matched strings. + */ + std::vector match(const std::string &query, const std::unordered_map> &index, int threshold) override; + +private: + /** + * @brief Calculates the edit distance between two strings. + * @param s1 The first string. + * @param s2 The second string. + * @return The edit distance between the two strings. + */ + int editDistance(const std::string &s1, const std::string &s2); +}; + +/** + * @brief Regular expression matching strategy. + */ +class RegexMatch : public MatchStrategy +{ +public: + /** + * @brief Matches the given query against the index using regular expressions. + * @param query The query string to match. + * @param index The index containing the data to match against. + * @param threshold The matching threshold (not used in this strategy). + * @return A vector of matched strings. + */ + std::vector match(const std::string &query, const std::unordered_map> &index, int /*threshold*/) override; +}; + +/** + * @brief Hamming distance matching strategy. + */ +class HammingMatch : public MatchStrategy +{ +public: + /** + * @brief Constructs a new HammingMatch object with the specified maximum distance. + * @param maxDistance The maximum Hamming distance allowed for a match. + */ + HammingMatch(int maxDistance); + + /** + * @brief Matches the given query against the index using Hamming distance. + * @param query The query string to match. + * @param index The index containing the data to match against. + * @param threshold The matching threshold (not used in this strategy). + * @return A vector of matched strings. + */ + std::vector match(const std::string &query, const std::unordered_map> &index, int /*threshold*/) override; + +private: + int maxDistance_; ///< The maximum Hamming distance allowed for a match. + + /** + * @brief Calculates the Hamming distance between two strings. + * @param s1 The first string. + * @param s2 The second string. + * @return The Hamming distance between the two strings. + */ + int hammingDistance(const std::string &s1, const std::string &s2); +}; + +/** + * @brief TF-IDF matching strategy. + */ +class TfIdfMatch : public MatchStrategy +{ +public: + /** + * @brief Constructs a new TfIdfMatch object with the given data. + * @param data The vector of strings to build the index from. + */ + TfIdfMatch(const std::vector &data); + + /** + * @brief Matches the given query against the index using TF-IDF. + * @param query The query string to match. + * @param index The index containing the data to match against. + * @param threshold The matching threshold (not used in this strategy). + * @return A vector of matched strings. + */ + std::vector match(const std::string &query, const std::unordered_map> &index, int /*threshold*/) override; + +private: + std::vector> termFrequency_; ///< The term frequency for each document. + std::unordered_map inverseDocumentFrequency_; ///< The inverse document frequency. + + /** + * @brief Builds the index from the given data. + * @param data The vector of strings to build the index from. + */ + void buildIndex(const std::vector &data); + + /** + * @brief Builds the inverse document frequency (IDF) map. + */ + void buildIdf(); + + /** + * @brief Calculates the term frequency (TF) for a single string. + * @param str The input string. + * @return The term frequency map for the input string. + */ + std::unordered_map calculateTf(const std::string &str); + + /** + * @brief Calculates the term frequency (TF) for a vector of strings. + * @param strList The input vector of strings. + * @return The term frequency map for the input vector of strings. + */ + std::unordered_map calculateTf(const std::vector &strList); + + /** + * @brief Calculates the TF-IDF score for a map of term frequency (TF). + * @param tf The term frequency map. + * @return The TF-IDF map. + */ + std::unordered_map calculateTfidf(const std::unordered_map &tf); + + /** + * @brief Calculates the TF-IDF score for a vector of strings. + * @param strList The input vector of strings. + * @return The TF-IDF map. + */ + std::unordered_map calculateTfidf(const std::vector &strList); + + /** + * @brief Calculates the cosine similarity between two TF-IDF maps. + * @param tfidf1 The first TF-IDF map. + * @param tfidf2 The second TF-IDF map. + * @return The cosine similarity between the two maps. + */ + double cosineSimilarity(const std::unordered_map &tfidf1, const std::unordered_map &tfidf2); +}; + +/** + * @brief Search engine class that uses a specific matching strategy. + */ +class SearchEngine +{ +public: + /** + * @brief Constructs a new SearchEngine object with the given data and matching strategy. + * @param data The vector of strings to build the index from. + * @param strategy The matching strategy to use. + */ + SearchEngine(const std::vector &data, MatchStrategy *strategy); + + /** + * @brief Sets the matching strategy to use. + * @param strategy The matching strategy to use. + */ + void setMatchStrategy(MatchStrategy *strategy); + + /** + * @brief Searches for matches to the given query using the current matching strategy. + * @param query The query string to search for. + * @param threshold The matching threshold (optional). + * @return A vector of matched strings. + */ + std::vector search(const std::string &query, int threshold); + + /** + * @brief Adds a new string to the index. + * @param str The string to add. + */ + void addData(const std::string &str); + + /** + * @brief Removes a string from the index. + * @param str The string to remove. + */ + void removeData(const std::string &str); + +private: + std::unordered_map> index_; ///< The index containing the data. + MatchStrategy *strategy_; ///< The matching strategy to use. + + /** + * @brief Builds the index from the given data. + * @param data The vector of strings to build the index from. + */ + void buildIndex(const std::vector &data); +}; + +/* +int main() +{ + std::vector data = {"apple", "banana", "orange", "watermelon", "pineapple", "mango", "peach", "pear", "grape", "kiwi"}; + FuzzyMatch fuzzyMatch; + RegexMatch regexMatch; + HammingMatch hammingMatch(2); + TfIdfMatch tfidfMatch(data); + + SearchEngine engine(data, &fuzzyMatch); + + std::string query = "appel"; + auto results = engine.search(query); + std::cout << "Fuzzy search results for '" << query << "':\n"; + for (const auto &str : results) + { + std::cout << "- " << str << "\n"; + } + + query = ".*e.*"; + engine.setMatchStrategy(®exMatch); + results = engine.search(query); + std::cout << "\nRegex search results for pattern '" << query << "':\n"; + for (const auto &str : results) + { + std::cout << "- " << str << "\n"; + } + + query = "appl"; + engine.setMatchStrategy(&hammingMatch); + results = engine.search(query); + std::cout << "\nHamming search results for '" << query << "':\n"; + for (const auto &str : results) + { + std::cout << "- " << str << "\n"; + } + + query = "apple"; + engine.setMatchStrategy(&tfidfMatch); + results = engine.search(query); + std::cout << "\nTF-IDF search results for '" << query << "':\n"; + for (const auto &str : results) + { + std::cout << "- " << str << "\n"; + } + + return 0; +} + +*/ diff --git a/src/controller/AsyncModuleController.hpp b/src/controller/AsyncModuleController.hpp index deb19902..5cfc7a92 100644 --- a/src/controller/AsyncModuleController.hpp +++ b/src/controller/AsyncModuleController.hpp @@ -10,6 +10,14 @@ #include "LithiumApp.hpp" #include "data/ModuleDto.hpp" +#include "data/StatusDto.hpp" + +#include "atom/plugin/module_loader.hpp" +#include "atom/server/global_ptr.hpp" +#include "core/plugin/plugin.hpp" +#include "core/device.hpp" + +#include "template/variable.hpp" #include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen @@ -36,6 +44,7 @@ class ModuleController : public oatpp::web::server::api::ApiController info->summary = "Load a plugin module from the specified path"; info->addConsumes>("application/json"); info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_400, "application/json"); } ENDPOINT_ASYNC("GET", "/api/module/load", getUILoadModule) { @@ -48,29 +57,268 @@ class ModuleController : public oatpp::web::server::api::ApiController Action returnResponse(const oatpp::Object& body) { auto res = StatusDto::createShared(); - if(body->plugin_path.getValue("") == "" || body->plugin_name.getValue("") == "") + OATPP_ASSERT_HTTP(body->plugin_path.getValue("")!= "", Status::CODE_400, "Invalid Parameters"); + OATPP_ASSERT_HTTP(body->plugin_type.getValue("")!= "", Status::CODE_400, "Invalid Parameters"); + OATPP_ASSERT_HTTP(body->plugin_name.getValue("")!= "", Status::CODE_400, "Invalid Parameters"); + auto plugin_path = body->plugin_path.getValue(""); + auto plugin_name = body->plugin_name.getValue(""); + auto plugin_type = body->plugin_type.getValue(""); + if (!Lithium::MyApp->loadModule(plugin_path, plugin_name)) + { + res->error = "ModuleError"; + res->message = fmt::format("Failed to load module: {}", plugin_name); + } + return _return(controller->createDtoResponse(Status::CODE_200, res)); + } + }; + + ENDPOINT_INFO(getUIUnloadModule) + { + info->summary = "Unload module by name"; + info->addConsumes>("application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + } + ENDPOINT_ASYNC("GET", "/api/module/unload", getUIUnloadModule) + { + ENDPOINT_ASYNC_INIT(getUIUnloadModule) + Action act() override + { + return request->readBodyToDtoAsync>(controller->getDefaultObjectMapper()).callbackTo(&getUIUnloadModule::returnResponse); + } + + Action returnResponse(const oatpp::Object& body) + { + auto res = StatusDto::createShared(); + OATPP_ASSERT_HTTP(body->plugin_name.getValue("")!= "", Status::CODE_400, "Invalid Parameters"); + auto plugin_name = body->plugin_name.getValue(""); + if (!Lithium::MyApp->unloadModule(plugin_name)) { - res->error = "Invalid Parameters"; - res->message = "Device plugin path and name is required"; + res->error = "ModuleError"; + res->message = fmt::format("Failed to unload module: {}", plugin_name); + } + return _return(controller->createDtoResponse(Status::CODE_200, res)); + } + }; + + ENDPOINT_INFO(getUIGetModuleList) + { + info->summary = "Get module list"; + info->addConsumes>("application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + } + ENDPOINT_ASYNC("GET", "/api/module/list", getUIGetModuleList) + { + ENDPOINT_ASYNC_INIT(getUIGetModuleList) + Action act() override + { + return request->readBodyToDtoAsync>(controller->getDefaultObjectMapper()).callbackTo(&getUIGetModuleList::returnResponse); + } + + Action returnResponse(const oatpp::Object& body) + { + auto res = ReturnModuleListDto::createShared(); + OATPP_ASSERT_HTTP(body->plugin_path.getValue("")!= "", Status::CODE_400, "Invalid Parameters"); + auto plugin_path = body->plugin_path.getValue(""); + auto module_list = Lithium::MyApp->getModuleList(); + for (auto module : module_list) + { + res->module_list->push_back(module); + OATPP_LOGD("ModuleController", "Module: %s", module.c_str()); + } + return _return(controller->createDtoResponse(Status::CODE_200, res)); + } + }; + + ENDPOINT_INFO(getUIRefreshModuleLists) + { + info->summary = "Refresh module list"; + info->addConsumes>("application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + } + ENDPOINT_ASYNC("GET", "/api/module/refresh", getUIRefreshModuleLists) + { + ENDPOINT_ASYNC_INIT(getUIRefreshModuleLists) + Action act() override + { + return request->readBodyToDtoAsync>(controller->getDefaultObjectMapper()).callbackTo(&getUIRefreshModuleLists::returnResponse); + } + + Action returnResponse(const oatpp::Object& body) + { + auto res = StatusDto::createShared(); + return _return(controller->createDtoResponse(Status::CODE_200, res)); + } + }; + + ENDPOINT_INFO(getUIEnableModule) + { + info->summary = "Enable module by name"; + info->addConsumes>("application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + } + ENDPOINT_ASYNC("GET", "/api/module/enable", getUIEnableModule) + { + ENDPOINT_ASYNC_INIT(getUIEnableModule) + Action act() override + { + return request->readBodyToDtoAsync>(controller->getDefaultObjectMapper()).callbackTo(&getUIEnableModule::returnResponse); + } + + Action returnResponse(const oatpp::Object& body) + { + auto res = StatusDto::createShared(); + OATPP_ASSERT_HTTP(body->plugin_name.getValue("")!= "", Status::CODE_400, "Invalid Parameters"); + auto plugin_name = body->plugin_name.getValue(""); + if (!Lithium::MyApp->enableModule(plugin_name)) + { + res->error = "ModuleError"; + res->message = fmt::format("Failed to enable module: {}", plugin_name); + } + return _return(controller->createDtoResponse(Status::CODE_200, res)); + } + }; + + ENDPOINT_INFO(getUIDisableModule) + { + info->summary = "Disable module by name"; + info->addConsumes>("application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + } + ENDPOINT_ASYNC("GET", "/api/module/disable", getUIDisableModule) + { + ENDPOINT_ASYNC_INIT(getUIDisableModule) + Action act() override + { + return request->readBodyToDtoAsync>(controller->getDefaultObjectMapper()).callbackTo(&getUIDisableModule::returnResponse); + } + + Action returnResponse(const oatpp::Object& body) + { + auto res = StatusDto::createShared(); + OATPP_ASSERT_HTTP(body->plugin_name.getValue("")!= "", Status::CODE_400, "Invalid Parameters"); + auto plugin_name = body->plugin_name.getValue(""); + if (!Lithium::MyApp->disableModule(plugin_name)) + { + res->error = "ModuleError"; + res->message = fmt::format("Failed to disable module: {}", plugin_name); + } + } + }; + + ENDPOINT_INFO(getUIGetModuleStatus) + { + info->summary = "Get module status"; + info->addConsumes>("application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + } + ENDPOINT_ASYNC("GET", "/api/module/status", getUIGetModuleStatus) + { + ENDPOINT_ASYNC_INIT(getUIGetModuleStatus) + Action act() override + { + return request->readBodyToDtoAsync>(controller->getDefaultObjectMapper()).callbackTo(&getUIGetModuleStatus::returnResponse); + } + + Action returnResponse(const oatpp::Object& body) + { + auto res = ReturnModuleStatusDto::createShared(); + OATPP_ASSERT_HTTP(body->module_name.getValue("")!= "", Status::CODE_400, "Invalid Parameters"); + auto module_name = body->module_name.getValue(""); + auto module_status = Lithium::MyApp->getModuleStatus(module_name); + if (module_status) + { + res->module_status = module_status; } else { - auto plugin_path = body->plugin_path.getValue(""); - auto plugin_name = body->plugin_name.getValue(""); - if (!Lithium::MyApp->LoadModule(plugin_path, plugin_name)) - { - res->error = "DeviceError"; - res->message = "Failed to add device plugin"; - } + res->error = "ModuleError"; + res->message = fmt::format("Failed to get module status: {}", module_name); } return _return(controller->createDtoResponse(Status::CODE_200, res)); } }; - ENDPOINT_INFO(getUIUnloadModule) + ENDPOINT_INFO(getUIGetModuleConfig) { - info->summary = "Unload module by name"; + info->summary = "Get module config"; + info->addConsumes>("application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + } + ENDPOINT_ASYNC("GET", "/api/module/config", getUIGetModuleConfig) + { + ENDPOINT_ASYNC_INIT(getUIGetModuleConfig) + Action act() override + { + return request->readBodyToDtoAsync>(controller->getDefaultObjectMapper()).callbackTo(&getUIGetModuleConfig::returnResponse); + } + + Action returnResponse(const oatpp::Object& body) + { + auto res = ReturnModuleConfigDto::createShared(); + OATPP_ASSERT_HTTP(body->module_name.getValue("")!= "", Status::CODE_400, "Invalid Parameters"); + auto module_name = body->module_name.getValue(""); + auto module_config = Lithium::MyApp->getModuleConfig(module_name); + if (!module_config.empty()) + { + res->module_config = module_config.dump(4); + } + else + { + res->error = "ModuleError"; + res->message = fmt::format("Failed to get module config: {}", module_name); + } + return _return(controller->createDtoResponse(Status::CODE_200, res)); + } + }; + + // This function is very dangerous, please use it carefully + // It will return the shared_ptr of the module, but we will do nothing with it + // So please make sure you know what you are doing + ENDPOINT_INFO(getUIGetInstance) + { + info->summary = "Get shared_ptr from a specific module and register it into global ptr mamanger"; + info->addConsumes>("application/json"); + info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_200, "application/json"); } + ENDPOINT_ASYNC("GET","/api/module/get/instance", getUIGetInstance) + { + ENDPOINT_ASYNC_INIT(getUIGetInstance) + Action act() override + { + return request->readBodyToDtoAsync>(controller->getDefaultObjectMapper()).callbackTo(&getUIGetInstance::returnResponse); + } + + Action returnResponse(const oatpp::Object& body) + { + auto res = ReturnModuleConfigDto::createShared(); + CHECK_VARIABLE(module_name,"Invalid Parameters") + CHECK_VARIABLE(instance_name,"Invalid Parameters") + CHECK_VARIABLE(instance_type,"Invalid Parameters") + CHECK_VARIABLE(get_func,"Invalid Parameters") + if (instance_type == "plugin" || instance_type == "module") + { + AddPtr(instance_name, GetPtr("ModuleLoader")->GetInstance(module_name, {}, instance_type)); + } + else if (instance_type == "device") + { + AddPtr(instance_name, GetPtr("ModuleLoader")->GetInstance(module_name, {}, instance_type)); + } + else + { + res->error = "ModuleError"; + res->message = fmt::format("Failed to get instance: {}", instance_name); + } + return _return(controller->createDtoResponse(Status::CODE_200, res)); + } + }; + }; #include OATPP_CODEGEN_END(ApiController) //<- End Codegen diff --git a/src/controller/template/variable.hpp b/src/controller/template/variable.hpp new file mode 100644 index 00000000..65a59635 --- /dev/null +++ b/src/controller/template/variable.hpp @@ -0,0 +1,5 @@ +#pragma once + +#define CHECK_VARIABLE(name, message) \ + OATPP_ASSERT_HTTP(body->name.getValue("") != "", Status::CODE_400, message); \ + auto name = body->name.getValue(""); \ No newline at end of file diff --git a/src/data/ModuleDto.hpp b/src/data/ModuleDto.hpp index 82952d1f..83a3364f 100644 --- a/src/data/ModuleDto.hpp +++ b/src/data/ModuleDto.hpp @@ -35,11 +35,13 @@ Description: Data Transform Object for Module Controller #include "oatpp/core/Types.hpp" #include "oatpp/core/macro/codegen.hpp" +#include "StatusDto.hpp" + #include OATPP_CODEGEN_BEGIN(DTO) ///< Begin DTO codegen section -class LoadPluginDTO : public oatpp::DTO +class LoadPluginDto : public oatpp::DTO { - DTO_INIT(LoadPluginDTO, DTO) + DTO_INIT(LoadPluginDto, DTO) DTO_FIELD_INFO(plugin_path) { @@ -70,6 +72,188 @@ class LoadPluginDTO : public oatpp::DTO DTO_FIELD(Boolean, need_check); }; +class UnloadPluginDto : public oatpp::DTO +{ + DTO_INIT(UnloadPluginDto, DTO) + + DTO_FIELD_INFO(plugin_name) + { + info->description = "Name of the device plugin to remove"; + info->required = true; + } + DTO_FIELD(String, plugin_name); +}; + +class GetModuleListDto : public oatpp::DTO +{ + DTO_INIT(GetModuleListDto, DTO) + + DTO_FIELD_INFO(plugin_path) + { + info->description = "Path of the module to get"; + info->required = true; + } + DTO_FIELD(String, plugin_path); +}; + +class ReturnModuleListDto : public StatusDto +{ + DTO_INIT(ReturnModuleListDto, DTO) + + DTO_FIELD_INFO(module_list) + { + info->description = "List of the modules"; + info->required = true; + } + DTO_FIELD(Vector, module_list); +}; + +class RefreshModuleListDto : public StatusDto +{ + DTO_INIT(RefreshModuleListDto, DTO) +}; + +class GetEnableModuleDto : public oatpp::DTO +{ + DTO_INIT(GetEnableModuleDto, DTO) + + DTO_FIELD_INFO(plugin_name) + { + info->description = "Name of the module to enable"; + info->required = true; + } + DTO_FIELD(String, plugin_name); +}; + +class ReturnEnableModuleDto : public StatusDto +{ + DTO_INIT(ReturnEnableModuleDto, DTO) + + DTO_FIELD_INFO(enable_module) + { + info->description = "Enable status of the module"; + info->required = true; + } + DTO_FIELD(Boolean, enable_module); +}; + +class GetDisableModuleDto : public oatpp::DTO +{ + DTO_INIT(GetDisableModuleDto, DTO) + + DTO_FIELD_INFO(plugin_name) + { + info->description = "Name of the module to disable"; + info->required = true; + } + DTO_FIELD(String, plugin_name); +}; + +class ReturnDisableModuleDto : public StatusDto +{ + DTO_INIT(ReturnDisableModuleDto, DTO) + + DTO_FIELD_INFO(disable_module) + { + info->description = "Disable status of the module"; + info->required = true; + } + DTO_FIELD(Boolean, disable_module); +}; + +class GetModuleStatusDto : public oatpp::DTO +{ + DTO_INIT(GetModuleStatusDto, DTO) + + DTO_FIELD_INFO(module_name) + { + info->description = "Name of the module to get"; + info->required = true; + } + DTO_FIELD(String, module_name); +}; + +class ReturnModuleStatusDto : public StatusDto +{ + DTO_INIT(ReturnModuleStatusDto, DTO) + + DTO_FIELD_INFO(module_status) + { + info->description = "Status of the module"; + info->required = true; + } + DTO_FIELD(Boolean, module_status); +}; + +class GetModuleConfigDto : public oatpp::DTO +{ + DTO_INIT(GetModuleConfigDto, DTO) + + DTO_FIELD_INFO(module_name) + { + info->description = "Name of the module to get"; + info->required = true; + } + DTO_FIELD(String, module_name); +}; + +class ReturnModuleConfigDto : public StatusDto +{ + DTO_INIT(ReturnModuleConfigDto, DTO) + + DTO_FIELD_INFO(module_config) + { + info->description = "Config of the module"; + info->required = true; + } + DTO_FIELD(String, module_config); +}; + +class GetInstanceDto : public oatpp::DTO +{ + DTO_INIT(GetInstanceDto, DTO) + + DTO_FIELD_INFO(module_name) + { + info->description = "Name of the module to get"; + info->required = true; + } + DTO_FIELD(String, module_name); + + DTO_FIELD_INFO(instance_name) + { + info->description = "Name of the instance to get"; + info->required = true; + } + DTO_FIELD(String, instance_name); + + DTO_FIELD_INFO(instance_type) + { + info->description = "Type of the instance to get"; + info->required = true; + } + DTO_FIELD(String, instance_type); + + DTO_FIELD_INFO(get_func) + { + info->description = "Function to get the instance"; + info->required = true; + } + DTO_FIELD(String, get_func); +}; + +class ReturnInstanceDto : public StatusDto +{ + DTO_INIT(ReturnInstanceDto, DTO) + + DTO_FIELD_INFO(instance) + { + info->description = "Instance of the module"; + info->required = true; + } + DTO_FIELD(String, instance); +}; + #include OATPP_CODEGEN_END(DTO) ///< End DTO codegen section #endif \ No newline at end of file From 0b58e1646dcf430d8584cb66a2deb24c2e03c90c Mon Sep 17 00:00:00 2001 From: Max Qian Date: Thu, 7 Dec 2023 13:49:17 +0800 Subject: [PATCH 2/9] update for submodule --- .github/workflows/codeql.yml | 2 +- modules/CMakeLists.txt | 3 - modules/hydrogen_client/CMakeLists.txt | 142 - .../hydrogen_client/abstractbaseclient.cpp | 630 - modules/hydrogen_client/abstractbaseclient.h | 240 - .../hydrogen_client/abstractbaseclient_p.h | 103 - modules/hydrogen_client/baseclient.cpp | 371 - modules/hydrogen_client/baseclient.h | 84 - modules/hydrogen_client/baseclient_p.h | 72 - modules/hydrogen_client/baseclientqt.cpp | 163 - modules/hydrogen_client/baseclientqt.h | 71 - modules/hydrogen_client/baseclientqt_p.h | 48 - modules/hydrogen_client/socket/CMakeLists.txt | 45 - modules/hydrogen_client/socket/select.h | 233 - modules/hydrogen_client/socket/tcpsocket.cpp | 541 - modules/hydrogen_client/socket/tcpsocket.h | 121 - modules/hydrogen_client/socket/tcpsocket_p.h | 134 - .../hydrogen_client/socket/tcpsocket_unix.cpp | 75 - .../hydrogen_client/socket/tcpsocket_win.cpp | 64 - modules/hydrogen_loader/CMakeLists.txt | 57 - modules/hydrogen_loader/README.md | 52 - modules/hydrogen_loader/client_info.cpp | 314 - modules/hydrogen_loader/client_info.hpp | 60 - modules/hydrogen_loader/concurrent.cpp | 0 modules/hydrogen_loader/concurrent.hpp | 145 - modules/hydrogen_loader/driver_info.cpp | 305 - modules/hydrogen_loader/driver_info.hpp | 81 - modules/hydrogen_loader/fifo_server.cpp | 451 - modules/hydrogen_loader/fifo_server.hpp | 64 - modules/hydrogen_loader/hydrogen_server.cpp | 422 - modules/hydrogen_loader/hydrogen_server.hpp | 52 - modules/hydrogen_loader/io.cpp | 130 - modules/hydrogen_loader/io.hpp | 16 - modules/hydrogen_loader/local_driver.cpp | 535 - modules/hydrogen_loader/local_driver.hpp | 55 - modules/hydrogen_loader/message.cpp | 272 - modules/hydrogen_loader/message.hpp | 160 - modules/hydrogen_loader/message_queue.cpp | 900 - modules/hydrogen_loader/message_queue.hpp | 113 - modules/hydrogen_loader/property.cpp | 30 - modules/hydrogen_loader/property.hpp | 46 - modules/hydrogen_loader/remote_driver.cpp | 172 - modules/hydrogen_loader/remote_driver.hpp | 30 - modules/hydrogen_loader/serialize.cpp | 583 - modules/hydrogen_loader/serialize.hpp | 167 - modules/hydrogen_loader/signal.cpp | 64 - modules/hydrogen_loader/signal.hpp | 28 - modules/hydrogen_loader/tcp_server.cpp | 249 - modules/hydrogen_loader/tcp_server.hpp | 44 - modules/hydrogen_loader/time.cpp | 22 - modules/hydrogen_loader/time.hpp | 3 - modules/hydrogen_loader/unix_server.cpp | 149 - modules/hydrogen_loader/unix_server.hpp | 28 - modules/hydrogen_loader/xml_util.cpp | 68 - modules/hydrogen_loader/xml_util.hpp | 20 - modules/lithium_image/CMakeLists.txt | 126 - modules/lithium_image/cimg/draw.cpp | 895 - modules/lithium_image/cimg/image.cpp | 674 - modules/lithium_image/cimg/image.hpp | 87 - modules/lithium_image/config.h.in | 3 - modules/lithium_image/config.json | 20 - modules/lithium_image/main.cpp | 59 - modules/lithium_image/opencv/image.cpp | 482 - modules/lithium_image/opencv/image.hpp | 21 - modules/lithium_image/opencv/pimage.cpp | 837 - modules/lithium_search/CMakeLists.txt | 126 - modules/lithium_search/config.h.in | 3 - modules/lithium_search/config.json | 20 - modules/lithium_search/main.cpp | 55 - modules/lithium_search/ongc/dso.cpp | 244 - modules/lithium_search/ongc/dso.hpp | 117 - modules/lithium_search/ongc/ongc.cpp | 450 - modules/lithium_search/ongc/ongc.hpp | 48 - modules/lithium_search/ongc/ongc_utils.cpp | 69 - modules/lithium_search/ongc/ongc_utils.hpp | 7 - modules/oxygen_celestial/calculator.py | 51 - modules/oxygen_celestial/createdb.py | 140 - modules/oxygen_celestial/data.csv | 13969 ---------------- modules/oxygen_celestial/data.db | Bin 1568768 -> 0 bytes modules/oxygen_celestial/gps3.py | 196 - modules/oxygen_celestial/location.conf | 4 - modules/oxygen_celestial/object.py | 612 - modules/oxygen_celestial/search.py | 926 - modules/oxygen_celestial/time.py | 49 - modules/oxygen_celestial/vgps.py | 150 - modules/oxygen_celestial/view.sql | 1 - 86 files changed, 1 insertion(+), 29469 deletions(-) delete mode 100644 modules/CMakeLists.txt delete mode 100644 modules/hydrogen_client/CMakeLists.txt delete mode 100644 modules/hydrogen_client/abstractbaseclient.cpp delete mode 100644 modules/hydrogen_client/abstractbaseclient.h delete mode 100644 modules/hydrogen_client/abstractbaseclient_p.h delete mode 100644 modules/hydrogen_client/baseclient.cpp delete mode 100644 modules/hydrogen_client/baseclient.h delete mode 100644 modules/hydrogen_client/baseclient_p.h delete mode 100644 modules/hydrogen_client/baseclientqt.cpp delete mode 100644 modules/hydrogen_client/baseclientqt.h delete mode 100644 modules/hydrogen_client/baseclientqt_p.h delete mode 100644 modules/hydrogen_client/socket/CMakeLists.txt delete mode 100644 modules/hydrogen_client/socket/select.h delete mode 100644 modules/hydrogen_client/socket/tcpsocket.cpp delete mode 100644 modules/hydrogen_client/socket/tcpsocket.h delete mode 100644 modules/hydrogen_client/socket/tcpsocket_p.h delete mode 100644 modules/hydrogen_client/socket/tcpsocket_unix.cpp delete mode 100644 modules/hydrogen_client/socket/tcpsocket_win.cpp delete mode 100644 modules/hydrogen_loader/CMakeLists.txt delete mode 100644 modules/hydrogen_loader/README.md delete mode 100644 modules/hydrogen_loader/client_info.cpp delete mode 100644 modules/hydrogen_loader/client_info.hpp delete mode 100644 modules/hydrogen_loader/concurrent.cpp delete mode 100644 modules/hydrogen_loader/concurrent.hpp delete mode 100644 modules/hydrogen_loader/driver_info.cpp delete mode 100644 modules/hydrogen_loader/driver_info.hpp delete mode 100644 modules/hydrogen_loader/fifo_server.cpp delete mode 100644 modules/hydrogen_loader/fifo_server.hpp delete mode 100644 modules/hydrogen_loader/hydrogen_server.cpp delete mode 100644 modules/hydrogen_loader/hydrogen_server.hpp delete mode 100644 modules/hydrogen_loader/io.cpp delete mode 100644 modules/hydrogen_loader/io.hpp delete mode 100644 modules/hydrogen_loader/local_driver.cpp delete mode 100644 modules/hydrogen_loader/local_driver.hpp delete mode 100644 modules/hydrogen_loader/message.cpp delete mode 100644 modules/hydrogen_loader/message.hpp delete mode 100644 modules/hydrogen_loader/message_queue.cpp delete mode 100644 modules/hydrogen_loader/message_queue.hpp delete mode 100644 modules/hydrogen_loader/property.cpp delete mode 100644 modules/hydrogen_loader/property.hpp delete mode 100644 modules/hydrogen_loader/remote_driver.cpp delete mode 100644 modules/hydrogen_loader/remote_driver.hpp delete mode 100644 modules/hydrogen_loader/serialize.cpp delete mode 100644 modules/hydrogen_loader/serialize.hpp delete mode 100644 modules/hydrogen_loader/signal.cpp delete mode 100644 modules/hydrogen_loader/signal.hpp delete mode 100644 modules/hydrogen_loader/tcp_server.cpp delete mode 100644 modules/hydrogen_loader/tcp_server.hpp delete mode 100644 modules/hydrogen_loader/time.cpp delete mode 100644 modules/hydrogen_loader/time.hpp delete mode 100644 modules/hydrogen_loader/unix_server.cpp delete mode 100644 modules/hydrogen_loader/unix_server.hpp delete mode 100644 modules/hydrogen_loader/xml_util.cpp delete mode 100644 modules/hydrogen_loader/xml_util.hpp delete mode 100644 modules/lithium_image/CMakeLists.txt delete mode 100644 modules/lithium_image/cimg/draw.cpp delete mode 100644 modules/lithium_image/cimg/image.cpp delete mode 100644 modules/lithium_image/cimg/image.hpp delete mode 100644 modules/lithium_image/config.h.in delete mode 100644 modules/lithium_image/config.json delete mode 100644 modules/lithium_image/main.cpp delete mode 100644 modules/lithium_image/opencv/image.cpp delete mode 100644 modules/lithium_image/opencv/image.hpp delete mode 100644 modules/lithium_image/opencv/pimage.cpp delete mode 100644 modules/lithium_search/CMakeLists.txt delete mode 100644 modules/lithium_search/config.h.in delete mode 100644 modules/lithium_search/config.json delete mode 100644 modules/lithium_search/main.cpp delete mode 100644 modules/lithium_search/ongc/dso.cpp delete mode 100644 modules/lithium_search/ongc/dso.hpp delete mode 100644 modules/lithium_search/ongc/ongc.cpp delete mode 100644 modules/lithium_search/ongc/ongc.hpp delete mode 100644 modules/lithium_search/ongc/ongc_utils.cpp delete mode 100644 modules/lithium_search/ongc/ongc_utils.hpp delete mode 100644 modules/oxygen_celestial/calculator.py delete mode 100644 modules/oxygen_celestial/createdb.py delete mode 100644 modules/oxygen_celestial/data.csv delete mode 100644 modules/oxygen_celestial/data.db delete mode 100644 modules/oxygen_celestial/gps3.py delete mode 100644 modules/oxygen_celestial/location.conf delete mode 100644 modules/oxygen_celestial/object.py delete mode 100644 modules/oxygen_celestial/search.py delete mode 100644 modules/oxygen_celestial/time.py delete mode 100644 modules/oxygen_celestial/vgps.py delete mode 100644 modules/oxygen_celestial/view.sql diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bab4c28a..4e9fe33d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -44,7 +44,7 @@ jobs: run: sudo apt-get update && sudo apt-get upgrade -y - name: Install dependencies - run: sudo apt install gcc g++ cmake libcfitsio-dev zlib1g-dev libssl-dev libzip-dev libnova-dev libfmt-dev gettext libintl-dev + run: sudo apt install gcc g++ cmake libcfitsio-dev zlib1g-dev libssl-dev libzip-dev libnova-dev libfmt-dev gettext - name: Checkout repository uses: actions/checkout@v3 diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt deleted file mode 100644 index aa349db4..00000000 --- a/modules/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_subdirectory(lithium_image) - -add_subdirectory(lithium_search) \ No newline at end of file diff --git a/modules/hydrogen_client/CMakeLists.txt b/modules/hydrogen_client/CMakeLists.txt deleted file mode 100644 index dd1454f5..00000000 --- a/modules/hydrogen_client/CMakeLists.txt +++ /dev/null @@ -1,142 +0,0 @@ -cmake_minimum_required(VERSION 3.13) -project(hydrogenclient CXX) - -include_directories(${CMAKE_SOURCE_DIR}/src/) -include_directories(${CMAKE_SOURCE_DIR}/src/core) -include_directories(${CMAKE_SOURCE_DIR}/src/core/base) -include_directories(${CMAKE_SOURCE_DIR}/src/core/io) -include_directories(${CMAKE_SOURCE_DIR}/src/core/property) -include_directories(${CMAKE_SOURCE_DIR}/src/core/timer) -include_directories(${CMAKE_SOURCE_DIR}/src/core/thread) - -include_directories(socket) -add_subdirectory(socket) - -add_library(hydrogenabstractclient OBJECT) - -# Headers -list(APPEND hydrogenabstractclient_HEADERS - abstractbaseclient.h -) - -list(APPEND hydrogenabstractclient_PRIVATE_HEADERS - abstractbaseclient_p.h -) - -# Sources -list(APPEND hydrogenabstractclient_SOURCES - abstractbaseclient.cpp -) - -# Setup Target -target_sources(hydrogenabstractclient - PUBLIC - ${hydrogenabstractclient_HEADERS} - PRIVATE - ${hydrogenabstractclient_SOURCES} - ${hydrogenabstractclient_PRIVATE_HEADERS} -) - -target_include_directories(hydrogenabstractclient - PUBLIC . -) - -target_link_libraries(hydrogenabstractclient hydrogencore) - -install(FILES - ${hydrogenabstractclient_HEADERS} - DESTINATION - ${INCLUDE_INSTALL_DIR}/libhydrogen - COMPONENT Devel -) - - -# Dependency -find_package(Threads REQUIRED) -find_package(ZLIB REQUIRED) -include_directories(${ZLIB_INCLUDE_DIR}) - -list(APPEND ${PROJECT_NAME}_LIBS - hydrogencore - hydrogenabstractclient - sockets - ${CMAKE_THREAD_LIBS_INIT} - ${ZLIB_LIBRARY} -) - -# Sources -list(APPEND ${PROJECT_NAME}_SOURCES - baseclient.cpp -) - -# Headers -list(APPEND ${PROJECT_NAME}_HEADERS - baseclient.h -) - -# Private Headers -list(APPEND ${PROJECT_NAME}_PRIVATE_HEADERS - baseclient_p.h -) - -# Build Object Library -add_library(${PROJECT_NAME}_OBJECT OBJECT) -set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) - -target_include_directories(${PROJECT_NAME}_OBJECT - PUBLIC . -) - -target_sources(${PROJECT_NAME}_OBJECT - PUBLIC - ${${PROJECT_NAME}_HEADERS} - PRIVATE - ${${PROJECT_NAME}_SOURCES} - ${${PROJECT_NAME}_PRIVATE_HEADERS} -) - -target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) - -install(FILES - ${${PROJECT_NAME}_HEADERS} - DESTINATION - ${INCLUDE_INSTALL_DIR}/libhydrogen - COMPONENT Devel -) - - add_library(${PROJECT_NAME}static STATIC) - - target_link_libraries(${PROJECT_NAME}static ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) - target_include_directories(${PROJECT_NAME}static - PUBLIC . - ) - - set_target_properties(${PROJECT_NAME}static PROPERTIES - VERSION ${CMAKE_HYDROGEN_VERSION_STRING} - SOVERSION ${HYDROGEN_SOVERSION} - OUTPUT_NAME ${PROJECT_NAME} # this same name like shared library - backwards compatibility - ) - - install(TARGETS ${PROJECT_NAME}static - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) - -# Build Shared Library -if(HYDROGEN_BUILD_SHARED) - add_library(${PROJECT_NAME} SHARED) - - target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) - target_include_directories(${PROJECT_NAME} - PUBLIC . - ) - - set_target_properties(${PROJECT_NAME} PROPERTIES - VERSION ${CMAKE_HYDROGEN_VERSION_STRING} - SOVERSION ${HYDROGEN_SOVERSION} - OUTPUT_NAME ${PROJECT_NAME} - ) - - install(TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) -endif() diff --git a/modules/hydrogen_client/abstractbaseclient.cpp b/modules/hydrogen_client/abstractbaseclient.cpp deleted file mode 100644 index c1d784f7..00000000 --- a/modules/hydrogen_client/abstractbaseclient.cpp +++ /dev/null @@ -1,630 +0,0 @@ -/******************************************************************************* - Copyright(c) 2016 Jasem Mutlaq. All rights reserved. - Copyright(c) 2022 Pawel Soja - - HYDROGEN Qt Client - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#include "abstractbaseclient.h" -#include "abstractbaseclient_p.h" - -#include "basedevice.h" -#include "basedevice_p.h" - -#include "hydrogenuserio.hpp" -#include "locale/locale_compat.hpp" -#include "hydrogenstandardproperty.h" - -#if defined(_MSC_VER) -#define snprintf _snprintf -#pragma warning(push) -///@todo Introduce plattform hydrogenpendent safe functions as macros to fix this -#pragma warning(disable : 4996) -#endif - -namespace HYDROGEN -{ - -userio AbstractBaseClientPrivate::io; - -// AbstractBaseClientPrivate - -AbstractBaseClientPrivate::AbstractBaseClientPrivate(AbstractBaseClient *parent) - : parent(parent) -{ - io.write = [](void *user, const void * ptr, size_t count) -> ssize_t - { - auto self = static_cast(user); - return self->sendData(ptr, count); - }; - - io.vprintf = [](void *user, const char * format, va_list ap) -> int - { - auto self = static_cast(user); - char message[MAXRBUF]; - vsnprintf(message, MAXRBUF, format, ap); - return int(self->sendData(message, strlen(message))); - }; -} - -void AbstractBaseClientPrivate::clear() -{ - watchDevice.clearDevices(); - blobModes.clear(); -} - -int AbstractBaseClientPrivate::dispatchCommand(const LilXmlElement &root, char *errmsg) -{ - // Ignore echoed newXXX - if (root.tagName().find("new") == 0) - { - return 0; - } - - if (root.tagName() == "pingRequest") - { - parent->sendPingReply(root.getAttribute("uid")); - return 0; - } - - if (root.tagName() == "pingReply") - { - parent->newPingReply(root.getAttribute("uid").toString()); - return 0; - } - - if (root.tagName() == "message") - { - return messageCmd(root, errmsg); - } - - if (root.tagName() == "delProperty") - { - return delPropertyCmd(root, errmsg); - } - - // Just ignore any getProperties we might get - if (root.tagName() == "getProperties") - { - return HYDROGEN_PROPERTY_DUPLICATED; - } - - // If device is set to BLOB_ONLY, we ignore everything else - // not related to blobs - if ( - parent->getBLOBMode(root.getAttribute("device")) == B_ONLY && - root.tagName() != "defBLOBVector" && - root.tagName() != "setBLOBVector" - ) - { - return 0; - } - - return watchDevice.processXml(root, errmsg, [this] // create new device if nessesery - { - ParentDevice device(ParentDevice::Valid); - device.setMediator(parent); - return device; - }); -} - -int AbstractBaseClientPrivate::deleteDevice(const char *devName, char *errmsg) -{ - if (auto device = watchDevice.getDeviceByName(devName)) - { - device.detach(); - watchDevice.deleteDevice(device); - return 0; - } - snprintf(errmsg, MAXRBUF, "Device %s not found", devName); - return HYDROGEN_DEVICE_NOT_FOUND; -} - -/* delete the property in the given device, including widgets and data structs. - * when last property is deleted, delete the device too. - * if no property name attribute at all, delete the whole device regardless. - * return 0 if ok, else -1 with reason in errmsg[]. - */ -int AbstractBaseClientPrivate::delPropertyCmd(const LilXmlElement &root, char *errmsg) -{ - /* dig out device and optional property name */ - BaseDevice dp = watchDevice.getDeviceByName(root.getAttribute("device")); - - if (!dp.isValid()) - return HYDROGEN_DEVICE_NOT_FOUND; - - dp.checkMessage(root.handle()); - - const auto propertyName = root.getAttribute("name"); - - // Delete the whole device if propertyName does not exists - if (!propertyName.isValid()) - { - return deleteDevice(dp.getDeviceName(), errmsg); - } - - // Delete property if it exists - if (auto property = dp.getProperty(propertyName)) - { - if (sConnected) - { - dp.d_ptr->mediateRemoveProperty(property); - } - return dp.removeProperty(propertyName, errmsg); - } - - // Silently ignore B_ONLY clients. - if (blobModes.empty() || blobModes.front().blobMode == B_ONLY) - return 0; - snprintf(errmsg, MAXRBUF, "Cannot delete property %s as it is not defined yet. Check driver.", propertyName.toCString()); - return -1; -} - -/* a general message command received from the device. - * return 0 if ok, else -1 with reason in errmsg[]. - */ -int AbstractBaseClientPrivate::messageCmd(const LilXmlElement &root, char *errmsg) -{ - HYDROGEN_UNUSED(errmsg); - - BaseDevice dp = watchDevice.getDeviceByName(root.getAttribute("device")); - - if (dp.isValid()) - { - dp.checkMessage(root.handle()); - return 0; - } - - char msgBuffer[MAXRBUF]; - - auto timestamp = root.getAttribute("timestamp"); - auto message = root.getAttribute("message"); - - if (!message.isValid()) - { - strncpy(errmsg, "No message content found.", MAXRBUF); - return -1; - } - - if (timestamp.isValid()) - { - snprintf(msgBuffer, MAXRBUF, "%s: %s", timestamp.toCString(), message.toCString()); - } - else - { - char ts[32]; - struct tm *tp; - time_t t; - time(&t); - tp = gmtime(&t); - strftime(ts, sizeof(ts), "%Y-%m-%dT%H:%M:%S", tp); - snprintf(msgBuffer, MAXRBUF, "%s: %s", ts, message.toCString()); - } - - parent->newUniversalMessage(msgBuffer); - - return 0; -} - -void AbstractBaseClientPrivate::userIoGetProperties() -{ - if (watchDevice.isEmpty()) - { - IUUserIOGetProperties(&io, this, nullptr, nullptr); - if (verbose) - IUUserIOGetProperties(userio_file(), stderr, nullptr, nullptr); - } - else - { - for (const auto &deviceInfo : watchDevice /* first: device name, second: device info */) - { - // If there are no specific properties to watch, we watch the complete device - if (deviceInfo.second.properties.size() == 0) - { - IUUserIOGetProperties(&io, this, deviceInfo.first.c_str(), nullptr); - if (verbose) - IUUserIOGetProperties(userio_file(), stderr, deviceInfo.first.c_str(), nullptr); - } - else - { - for (const auto &oneProperty : deviceInfo.second.properties) - { - IUUserIOGetProperties(&io, this, deviceInfo.first.c_str(), oneProperty.c_str()); - if (verbose) - IUUserIOGetProperties(userio_file(), stderr, deviceInfo.first.c_str(), oneProperty.c_str()); - } - } - } - } -} - - -void AbstractBaseClientPrivate::setDriverConnection(bool status, const char *deviceName) -{ - BaseDevice drv = parent->getDevice(deviceName); - - if (!drv.isValid()) - { - IDLog("BaseClientQt: Error. Unable to find driver %s\n", deviceName); - return; - } - - auto drv_connection = drv.getSwitch(SP::CONNECTION); - - if (!drv_connection.isValid()) - return; - - // If we need to connect - if (status) - { - // If there is no need to do anything, i.e. already connected. - if (drv_connection[0].getState() == ISS_ON) - return; - - drv_connection.reset(); - drv_connection.setState(IPS_BUSY); - drv_connection[0].setState(ISS_ON); - drv_connection[1].setState(ISS_OFF); - - parent->sendNewSwitch(drv_connection); - } - else - { - // If there is no need to do anything, i.e. already disconnected. - if (drv_connection[1].getState() == ISS_ON) - return; - - drv_connection.reset(); - drv_connection.setState(IPS_BUSY); - drv_connection[0].setState(ISS_OFF); - drv_connection[1].setState(ISS_ON); - - parent->sendNewSwitch(drv_connection); - } -} - -BLOBMode *AbstractBaseClientPrivate::findBLOBMode(const std::string &device, const std::string &property) -{ - for (auto &blob : blobModes) - { - if (blob.device == device && (property.empty() || blob.property == property)) - return &blob; - } - - return nullptr; -} - -// AbstractBaseClient - -AbstractBaseClient::AbstractBaseClient(std::unique_ptr &&d) - : d_ptr_hydrogen(std::move(d)) -{ } - -AbstractBaseClient::~AbstractBaseClient() -{ } - -void AbstractBaseClient::setServer(const char *hostname, unsigned int port) -{ - D_PTR(AbstractBaseClient); - d->cServer = hostname; - d->cPort = port; -} - -const char *AbstractBaseClient::getHost() const -{ - D_PTR(const AbstractBaseClient); - return d->cServer.c_str(); -} - -int AbstractBaseClient::getPort() const -{ - D_PTR(const AbstractBaseClient); - return d->cPort; -} - -bool AbstractBaseClient::isServerConnected() const -{ - D_PTR(const AbstractBaseClient); - return d->sConnected; -} - -void AbstractBaseClient::setConnectionTimeout(uint32_t seconds, uint32_t microseconds) -{ - D_PTR(AbstractBaseClient); - d->timeout_sec = seconds; - d->timeout_us = microseconds; -} - -void AbstractBaseClient::setVerbose(bool enable) -{ - D_PTR(AbstractBaseClient); - d->verbose = enable; -} - -bool AbstractBaseClient::isVerbose() const -{ - D_PTR(const AbstractBaseClient); - return d->verbose; -} - -void AbstractBaseClient::watchDevice(const char *deviceName) -{ - D_PTR(AbstractBaseClient); - d->watchDevice.watchDevice(deviceName); -} - -void AbstractBaseClient::watchDevice(const char *deviceName, const std::function &callback) -{ - D_PTR(AbstractBaseClient); - d->watchDevice.watchDevice(deviceName, callback); -} - -void AbstractBaseClient::watchProperty(const char *deviceName, const char *propertyName) -{ - D_PTR(AbstractBaseClient); - d->watchDevice.watchProperty(deviceName, propertyName); -} - -void AbstractBaseClient::connectDevice(const char *deviceName) -{ - D_PTR(AbstractBaseClient); - d->setDriverConnection(true, deviceName); -} - -void AbstractBaseClient::disconnectDevice(const char *deviceName) -{ - D_PTR(AbstractBaseClient); - d->setDriverConnection(false, deviceName); -} - -BaseDevice AbstractBaseClient::getDevice(const char *deviceName) -{ - D_PTR(AbstractBaseClient); - return d->watchDevice.getDeviceByName(deviceName); -} - -std::vector AbstractBaseClient::getDevices() const -{ - D_PTR(const AbstractBaseClient); - return d->watchDevice.getDevices(); -} - -bool AbstractBaseClient::getDevices(std::vector &deviceList, uint16_t driverInterface ) -{ - D_PTR(AbstractBaseClient); - for (auto &it : d->watchDevice) - { - if (it.second.device.getDriverInterface() & driverInterface) - deviceList.push_back(it.second.device); - } - - return (deviceList.size() > 0); -} - - -void AbstractBaseClient::setBLOBMode(BLOBHandling blobH, const char *dev, const char *prop) -{ - D_PTR(AbstractBaseClient); - if (!dev[0]) - return; - - auto *bMode = d->findBLOBMode(std::string(dev), prop ? std::string(prop) : std::string()); - - if (bMode == nullptr) - { - BLOBMode newMode; - newMode.device = std::string(dev); - newMode.property = (prop ? std::string(prop) : std::string()); - newMode.blobMode = blobH; - d->blobModes.push_back(std::move(newMode)); - } - else - { - // If nothing changed, nothing to to do - if (bMode->blobMode == blobH) - return; - - bMode->blobMode = blobH; - } - - IUUserIOEnableBLOB(&d->io, d, dev, prop, blobH); -} - -BLOBHandling AbstractBaseClient::getBLOBMode(const char *dev, const char *prop) -{ - D_PTR(AbstractBaseClient); - BLOBHandling bHandle = B_ALSO; - - auto *bMode = d->findBLOBMode(dev, (prop ? std::string(prop) : std::string())); - - if (bMode) - bHandle = bMode->blobMode; - - return bHandle; -} - -void AbstractBaseClient::sendNewProperty(HYDROGEN::Property pp) -{ - D_PTR(AbstractBaseClient); - pp.setState(IPS_BUSY); - // #PS: TODO more generic - switch (pp.getType()) - { - case HYDROGEN_NUMBER: - IUUserIONewNumber(&d->io, d, pp.getNumber()->cast()); - break; - case HYDROGEN_SWITCH: - IUUserIONewSwitch(&d->io, d, pp.getSwitch()->cast()); - break; - case HYDROGEN_TEXT: - IUUserIONewText(&d->io, d, pp.getText()->cast()); - break; - case HYDROGEN_LIGHT: - IDLog("Light type is not supported to send\n"); - break; - case HYDROGEN_BLOB: - IUUserIONewBLOB(&d->io, d, pp.getBLOB()->cast()); - break; - case HYDROGEN_UNKNOWN: - IDLog("Unknown type of property to send\n"); - break; - } -} - -void AbstractBaseClient::sendNewText(HYDROGEN::Property pp) -{ - D_PTR(AbstractBaseClient); - AutoCNumeric locale; - - pp.setState(IPS_BUSY); - IUUserIONewText(&d->io, d, pp.getText()->cast()); -} - -void AbstractBaseClient::sendNewText(const char *deviceName, const char *propertyName, const char *elementName, - const char *text) -{ - auto tvp = getDevice(deviceName).getText(propertyName); - - if (!tvp.isValid()) - return; - - auto tp = tvp.findWidgetByName(elementName); - - if (!tp) - return; - - tp->setText(text); - - sendNewText(tvp); -} - -void AbstractBaseClient::sendNewNumber(HYDROGEN::Property pp) -{ - D_PTR(AbstractBaseClient); - AutoCNumeric locale; - pp.setState(IPS_BUSY); - IUUserIONewNumber(&d->io, d, pp.getNumber()->cast()); -} - -void AbstractBaseClient::sendNewNumber(const char *deviceName, const char *propertyName, const char *elementName, - double value) -{ - auto nvp = getDevice(deviceName).getNumber(propertyName); - - if (!nvp.isValid()) - return; - - auto np = nvp.findWidgetByName(elementName); - - if (!np) - return; - - np->setValue(value); - - sendNewNumber(nvp); -} - -void AbstractBaseClient::sendNewSwitch(HYDROGEN::Property pp) -{ - D_PTR(AbstractBaseClient); - pp.setState(IPS_BUSY); - IUUserIONewSwitch(&d->io, d, pp.getSwitch()->cast()); -} - -void AbstractBaseClient::sendNewSwitch(const char *deviceName, const char *propertyName, const char *elementName) -{ - auto svp = getDevice(deviceName).getSwitch(propertyName); - - if (!svp.isValid()) - return; - - auto sp = svp.findWidgetByName(elementName); - - if (!sp) - return; - - sp->setState(ISS_ON); - - sendNewSwitch(svp); -} - -void AbstractBaseClient::startBlob(const char *devName, const char *propName, const char *timestamp) -{ - D_PTR(AbstractBaseClient); - IUUserIONewBLOBStart(&d->io, d, devName, propName, timestamp); -} - -void AbstractBaseClient::sendOneBlob(HYDROGEN::WidgetViewBlob *blob) -{ - D_PTR(AbstractBaseClient); - IUUserIOBLOBContextOne( - &d->io, d, - blob->getName(), blob->getSize(), blob->getBlobLen(), blob->getBlob(), blob->getFormat() - ); -} - -void AbstractBaseClient::sendOneBlob(IBLOB *bp) -{ - sendOneBlob(HYDROGEN::WidgetViewBlob::cast(bp)); -} - -void AbstractBaseClient::sendOneBlob(const char *blobName, unsigned int blobSize, const char *blobFormat, - void *blobBuffer) -{ - D_PTR(AbstractBaseClient); - IUUserIOBLOBContextOne( - &d->io, d, - blobName, blobSize, blobSize, blobBuffer, blobFormat - ); -} - -void AbstractBaseClient::finishBlob() -{ - D_PTR(AbstractBaseClient); - IUUserIONewBLOBFinish(&d->io, d); -} - -void AbstractBaseClient::sendPingRequest(const char * uuid) -{ - D_PTR(AbstractBaseClient); - IUUserIOPingRequest(&d->io, d, uuid); -} - -void AbstractBaseClient::sendPingReply(const char * uuid) -{ - D_PTR(AbstractBaseClient); - IUUserIOPingReply(&d->io, d, uuid); -} - -void AbstractBaseClient::newPingReply(std::string uid) -{ - IDLog("Ping reply %s\n", uid.c_str()); -} - -void AbstractBaseClient::newUniversalMessage(std::string message) -{ - HYDROGEN_UNUSED(message); - // Do not log message to stderr, let child decide what to do with it. - //IDLog("%s\n", message.c_str()); -} - -} - -#if defined(_MSC_VER) -#undef snprintf -#pragma warning(pop) -#endif diff --git a/modules/hydrogen_client/abstractbaseclient.h b/modules/hydrogen_client/abstractbaseclient.h deleted file mode 100644 index 00a75e95..00000000 --- a/modules/hydrogen_client/abstractbaseclient.h +++ /dev/null @@ -1,240 +0,0 @@ -/******************************************************************************* - Copyright(c) 2016 Jasem Mutlaq. All rights reserved. - Copyright(c) 2022 Pawel Soja - - HYDROGEN Qt Client - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#pragma once - -#include "hydrogenbase.h" -#include "util/macro.hpp" -#include "hydrogenproperty.h" - -#include -#include -#include - -namespace HYDROGEN -{ - -class BaseDevice; -class AbstractBaseClientPrivate; -class AbstractBaseClient : public HYDROGEN::BaseMediator -{ - DECLARE_PRIVATE_D(d_ptr_hydrogen, AbstractBaseClient) - - public: - AbstractBaseClient() = delete; - virtual ~AbstractBaseClient(); - - public: - /** @brief Set the server host name and port - * @param hostname HYDROGEN server host name or IP address. - * @param port HYDROGEN server port. - */ - void setServer(const char *hostname, unsigned int port); - - /** @brief Get the server host name. */ - const char *getHost() const; - - /** @brief Get the port number */ - int getPort() const; - - public: - /** @brief Connect to HYDROGEN server. - * @returns True if the connection is successful, false otherwise. - * @note This function blocks until connection is either successull or unsuccessful. - */ - virtual bool connectServer() = 0; - - /** @brief Disconnect from HYDROGEN server. - * Any devices previously created will be deleted and memory cleared. - * @return True if disconnection is successful, false otherwise. - */ - virtual bool disconnectServer(int exit_code = 0) = 0; - - /** @brief Get status of the connection. */ - bool isServerConnected() const; - - /** @brief setConnectionTimeout Set connection timeout. By default it is 3 seconds. - * @param seconds seconds - * @param microseconds microseconds - */ - void setConnectionTimeout(uint32_t seconds, uint32_t microseconds); - - public: - /** @brief setVerbose Set verbose mode - * @param enable If true, enable FULL verbose output. Any XML message received, including BLOBs, are printed on - * standard output. Only use this for debugging purposes. - */ - void setVerbose(bool enable); - - /** @brief isVerbose Is client in verbose mode? - * @return Is client in verbose mode? - */ - bool isVerbose() const; - - public: - /** @brief Add a device to the watch list. - * - * A client may select to receive notifications of only a specific device or a set of devices. - * If the client encounters any of the devices set via this function, it will create a corresponding - * HYDROGEN::BaseDevice object to handle them. If no devices are watched, then all devices owned by HYDROGEN server - * will be created and handled. - */ - void watchDevice(const char *deviceName); - void watchDevice(const char *deviceName, const std::function &callback); - - /** @brief watchProperties Add a property to the watch list. When communicating with HYDROGEN server. - * - * The client calls so that only a particular - * property (or list of properties if more than one) are defined back to the client. This function - * will call watchDevice(deviceName) as well to limit the traffic to this device. - * - * @param propertyName Property to watch for. - */ - void watchProperty(const char *deviceName, const char *propertyName); - - public: - /** @brief Disconnect HYDROGEN driver - * @param deviceName Name of the device to disconnect. - */ - void connectDevice(const char *deviceName); - - /** @brief Disconnect HYDROGEN driver - @param deviceName Name of the device to disconnect. - */ - void disconnectDevice(const char *deviceName); - - public: - /** @param deviceName Name of device to search for in the list of devices owned by HYDROGEN server, - * @returns If \e deviceName exists, it returns an instance of the device. Otherwise, it returns NULL. - */ - HYDROGEN::BaseDevice getDevice(const char *deviceName); - - /** @returns Returns a vector of all devices created in the client. */ - std::vector getDevices() const; - - /** @brief getDevices Returns list of devices that belong to a particular @ref HYDROGEN::BaseDevice::DRIVER_INTERFACE "DRIVER_INTERFACE" class. - * - * For example, to get a list of guide cameras: - * @code{.cpp} - * std::vector guideCameras; - * getDevices(guideCameras, CCD_INTERFACE | GUIDER_INTERFACE); - * for (HYDROGEN::BaseDevice *device : guideCameras) - * cout << "Guide Camera Name: " << device->getDeviceName(); - * @endcode - * @param deviceList Supply device list to be filled by the function. - * @param driverInterface ORed DRIVER_INTERFACE values to select the desired class of devices. - * @return True if one or more devices are found for the supplied driverInterface, false if no matching devices found. - */ - bool getDevices(std::vector &deviceList, uint16_t driverInterface); - - public: - /** @brief Set Binary Large Object policy mode - * - * Set the BLOB handling mode for the client. The client may either receive: - *
    - *
  • Only BLOBS
  • - *
  • BLOBs mixed with normal messages
  • - *
  • Normal messages only, no BLOBs
  • - *
- * If \e dev and \e prop are supplied, then the BLOB handling policy is set for this particular device and property. - * if \e prop is NULL, then the BLOB policy applies to the whole device. - * - * @param blobH BLOB handling policy - * @param dev name of device, required. - * @param prop name of property, optional. - */ - void setBLOBMode(BLOBHandling blobH, const char *dev, const char *prop = nullptr); - - /** @brief getBLOBMode Get Binary Large Object policy mode IF set previously by setBLOBMode - * @param dev name of device. - * @param prop property name, can be NULL to return overall device policy if it exists. - * @return BLOB Policy, if not found, it always returns B_ALSO - */ - BLOBHandling getBLOBMode(const char *dev, const char *prop = nullptr); - - public: - /** @brief Send new Property command to server */ - void sendNewProperty(HYDROGEN::Property pp); - - /** @brief Send new Text command to server */ - void sendNewText(HYDROGEN::Property pp); - /** @brief Send new Text command to server */ - void sendNewText(const char *deviceName, const char *propertyName, const char *elementName, const char *text); - /** @brief Send new Number command to server */ - void sendNewNumber(HYDROGEN::Property pp); - /** @brief Send new Number command to server */ - void sendNewNumber(const char *deviceName, const char *propertyName, const char *elementName, double value); - /** @brief Send new Switch command to server */ - void sendNewSwitch(HYDROGEN::Property pp); - /** @brief Send new Switch command to server */ - void sendNewSwitch(const char *deviceName, const char *propertyName, const char *elementName); - - /** @brief Send opening tag for BLOB command to server */ - void startBlob(const char *devName, const char *propName, const char *timestamp); - /** @brief Send ONE blob content to server. The BLOB data in raw binary format and will be converted to base64 and sent to server */ - void sendOneBlob(IBLOB *bp); - void sendOneBlob(HYDROGEN::WidgetViewBlob *blob); - /** @brief Send ONE blob content to server. The BLOB data in raw binary format and will be converted to base64 and sent to server */ - void sendOneBlob(const char *blobName, unsigned int blobSize, const char *blobFormat, void *blobBuffer); - /** @brief Send closing tag for BLOB command to server */ - void finishBlob(); - - public: - /** @brief Send one ping request, the server will answer back with the same uuid - * @param uid This string will server as identifier for the reply - * @note reply will be dispatched to newPingReply - */ - void sendPingRequest(const char * uid); - - /** @brief Send a ping reply for the given uuid - * @note This should not be called directly, as it is already handled by baseclient - */ - void sendPingReply(const char * uid); - - protected: - /** @brief pingReply are sent by the server on response to pingReply (see above). - */ - virtual void newPingReply(std::string uid); - - protected: - /** - * @brief newUniversalMessage Universal messages are sent from HYDROGEN server without a specific device. It is addressed to the client overall. - * @param message content of message. - * @note The default implementation simply logs the message to stderr. Override to handle the message. - */ - virtual void newUniversalMessage(std::string message); - - protected: - friend class BaseClientPrivate; - friend class BaseClientQtPrivate; - - protected: - AbstractBaseClient(std::unique_ptr &&dd); - - protected: - std::unique_ptr d_ptr_hydrogen; -}; - -} - -#ifdef SWIG -%template(BaseDeviceVectorShared) std::vector; -#endif diff --git a/modules/hydrogen_client/abstractbaseclient_p.h b/modules/hydrogen_client/abstractbaseclient_p.h deleted file mode 100644 index 721a3ee1..00000000 --- a/modules/hydrogen_client/abstractbaseclient_p.h +++ /dev/null @@ -1,103 +0,0 @@ -/******************************************************************************* - Copyright(c) 2016 Jasem Mutlaq. All rights reserved. - Copyright(c) 2022 Pawel Soja - - HYDROGEN Qt Client - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#pragma once - -#include "watchdeviceproperty.h" -#include "hydrogendevapi.hpp" -#include "hydrogenuserio.hpp" -#include "hydrogenlilxml.hpp" - -#include -#include -#include -#include - -namespace HYDROGEN -{ - -struct BLOBMode -{ - std::string device; - std::string property; - BLOBHandling blobMode; -}; - -class AbstractBaseClient; -class AbstractBaseClientPrivate -{ - public: - AbstractBaseClientPrivate(AbstractBaseClient *parent); - virtual ~AbstractBaseClientPrivate() = default; - - public: - virtual ssize_t sendData(const void *data, size_t size) = 0; - - public: - void clear(); - - public: - /** @brief Dispatch command received from HYDROGEN server to respective devices handled by the client */ - int dispatchCommand(const HYDROGEN::LilXmlElement &root, char *errmsg); - - /** @brief Remove device */ - int deleteDevice(const char *devName, char *errmsg); - - /** @brief Delete property command */ - int delPropertyCmd(const HYDROGEN::LilXmlElement &root, char *errmsg); - - /** Process messages */ - int messageCmd(const HYDROGEN::LilXmlElement &root, char *errmsg); - - public: - void userIoGetProperties(); - - public: - /** @brief Connect/Disconnect to HYDROGEN driver - @param status If true, the client will attempt to turn on CONNECTION property within the driver (i.e. turn on the device). - Otherwise, CONNECTION will be turned off. - @param deviceName Name of the device to connect to. - */ - void setDriverConnection(bool status, const char *deviceName); - - public: - BLOBMode *findBLOBMode(const std::string &device, const std::string &property); - - public: - AbstractBaseClient *parent; - - std::list blobModes; - - std::string cServer {"localhost"}; - uint32_t cPort {7624}; - - std::atomic_bool sConnected {false}; - - bool verbose {false}; - - uint32_t timeout_sec {3}, timeout_us {0}; - - WatchDeviceProperty watchDevice; - - static userio io; -}; - -} diff --git a/modules/hydrogen_client/baseclient.cpp b/modules/hydrogen_client/baseclient.cpp deleted file mode 100644 index 33c64840..00000000 --- a/modules/hydrogen_client/baseclient.cpp +++ /dev/null @@ -1,371 +0,0 @@ -/******************************************************************************* - Copyright(c) 2011 Jasem Mutlaq. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#include "abstractbaseclient.h" -#include "abstractbaseclient_p.h" - -#include "baseclient.h" -#include "baseclient_p.h" - -#define MAXHYDROGENBUF 49152 -#define DISCONNECTION_DELAY_US 500000 -#define MAXFD_PER_MESSAGE 16 /* No more than 16 buffer attached to a message */ - -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY -# include "sharedblob_parse.h" -# include -# include -# include -#endif - -#ifndef __linux__ -# include -#endif - -namespace HYDROGEN -{ - -//ClientSharedBlobs -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY -ClientSharedBlobs::Blobs::~Blobs() -{ - releaseBlobUids(*this); -} - -void ClientSharedBlobs::enableDirectBlobAccess(const char * dev, const char * prop) -{ - if (dev == nullptr || !dev[0]) - { - directBlobAccess[""].insert(""); - return; - } - if (prop == nullptr || !prop[0]) - { - directBlobAccess[dev].insert(""); - } - else - { - directBlobAccess[dev].insert(prop); - } -} - -void ClientSharedBlobs::disableDirectBlobAccess() -{ - directBlobAccess.clear(); -} - -bool ClientSharedBlobs::parseAttachedBlobs(const HYDROGEN::LilXmlElement &root, ClientSharedBlobs::Blobs &blobs) -{ - // parse all elements in root that are attached. - // Create for each a new GUID and associate it in a global map - // modify the xml to add an attribute with the guid - for (auto &blobContent : root.getElementsByTagName("oneBLOB")) - { - auto attached = blobContent.getAttribute("attached"); - - if (attached.toString() != "true") - continue; - - auto device = root.getAttribute("dev"); - auto name = root.getAttribute("name"); - - blobContent.removeAttribute("attached"); - blobContent.removeAttribute("enclen"); - - if (incomingSharedBuffers.empty()) - { - return false; - } - - int fd = *incomingSharedBuffers.begin(); - incomingSharedBuffers.pop_front(); - - auto id = allocateBlobUid(fd); - blobs.push_back(id); - - // Put something here for later replacement - blobContent.removeAttribute("attached-data-id"); - blobContent.removeAttribute("attachment-direct"); - blobContent.addAttribute("attached-data-id", id.c_str()); - if (isDirectBlobAccess(device.toString(), name.toString())) - { - // If client support read-only shared blob, mark it here - blobContent.addAttribute("attachment-direct", "true"); - } - } - return true; -} - -bool ClientSharedBlobs::hasDirectBlobAccessEntry(const std::map> &directBlobAccess, - const std::string &dev, const std::string &prop) -{ - auto devAccess = directBlobAccess.find(dev); - if (devAccess == directBlobAccess.end()) - { - return false; - } - return devAccess->second.find(prop) != devAccess->second.end(); -} - -bool ClientSharedBlobs::isDirectBlobAccess(const std::string &dev, const std::string &prop) const -{ - return hasDirectBlobAccessEntry(directBlobAccess, "", "") - || hasDirectBlobAccessEntry(directBlobAccess, dev, "") - || hasDirectBlobAccessEntry(directBlobAccess, dev, prop); -} - -void ClientSharedBlobs::addIncomingSharedBuffer(int fd) -{ - incomingSharedBuffers.push_back(fd); -} - -void ClientSharedBlobs::clear() -{ - for (int fd : incomingSharedBuffers) - { - ::close(fd); - } - incomingSharedBuffers.clear(); -} - -void TcpSocketSharedBlobs::readyRead() -{ - char buffer[MAXHYDROGENBUF]; - - struct msghdr msgh; - struct iovec iov; - - int recvflag = MSG_DONTWAIT; -#ifdef __linux__ - recvflag |= MSG_CMSG_CLOEXEC; -#endif - - union - { - struct cmsghdr cmsgh; - /* Space large enough to hold an 'int' */ - char control[CMSG_SPACE(MAXFD_PER_MESSAGE * sizeof(int))]; - } control_un; - - iov.iov_base = buffer; - iov.iov_len = MAXHYDROGENBUF; - - msgh.msg_name = NULL; - msgh.msg_namelen = 0; - msgh.msg_iov = &iov; - msgh.msg_iovlen = 1; - msgh.msg_flags = 0; - msgh.msg_control = control_un.control; - msgh.msg_controllen = sizeof(control_un.control); - - int n = recvmsg(reinterpret_cast(socketDescriptor()), &msgh, recvflag); - - if (n >= 0) - { - for (struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msgh); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msgh, cmsg)) - { - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) - { - int fdCount = 0; - while(cmsg->cmsg_len >= CMSG_LEN((fdCount + 1) * sizeof(int))) - { - fdCount++; - } - //IDLog("Received %d fds\n", fdCount); - int * fds = reinterpret_cast(CMSG_DATA(cmsg)); - for(int i = 0; i < fdCount; ++i) - { - int fd = fds[i]; - //IDLog("Received fd %d\n", fd); -#ifndef __linux__ - fcntl(fds[i], F_SETFD, FD_CLOEXEC); -#endif - sharedBlobs.addIncomingSharedBuffer(fd); - } - } - else - { - IDLog("Ignoring ancillary data level %d, type %d\n", cmsg->cmsg_level, cmsg->cmsg_type); - } - } - } - - if (n <= 0) - { - setSocketError(TcpSocket::ConnectionRefusedError); - return; - } - - emitData(buffer, n); -} -#endif -// BaseClientPrivate - -BaseClientPrivate::BaseClientPrivate(BaseClient *parent) - : AbstractBaseClientPrivate(parent) -{ - clientSocket.onData([this](const char *data, size_t size) - { - char msg[MAXRBUF]; - auto documents = xmlParser.parseChunk(data, size); - - if (documents.size() == 0) - { - if (xmlParser.hasErrorMessage()) - { - IDLog("Bad XML from %s/%d: %s\n%.*s\n", cServer.c_str(), cPort, xmlParser.errorMessage(), int(size), data); - } - return; - } - - for (const auto &doc : documents) - { - LilXmlElement root = doc.root(); - - if (verbose) - root.print(stderr, 0); - -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - ClientSharedBlobs::Blobs blobs; - - if (!clientSocket.sharedBlobs.parseAttachedBlobs(root, blobs)) - { - IDLog("Missing attachment from %s/%d\n", cServer.c_str(), cPort); - return; - } -#endif - - int err_code = dispatchCommand(root, msg); - - if (err_code < 0) - { - // Silenty ignore property duplication errors - if (err_code != HYDROGEN_PROPERTY_DUPLICATED) - { - IDLog("Dispatch command error(%d): %s\n", err_code, msg); - root.print(stderr, 0); - } - } - } - }); - - clientSocket.onErrorOccurred([this] (TcpSocket::SocketError) - { - if (sConnected == false) - return; - - this->parent->serverDisconnected(-1); - clear(); - watchDevice.unwatchDevices(); - }); -} - -BaseClientPrivate::~BaseClientPrivate() -{ } - -ssize_t BaseClientPrivate::sendData(const void *data, size_t size) -{ - return clientSocket.write(static_cast(data), size); -} - -// BaseClient - -BaseClient::BaseClient() - : AbstractBaseClient(std::unique_ptr(new BaseClientPrivate(this))) -{ } - -BaseClient::~BaseClient() -{ - D_PTR(BaseClient); - d->clear(); -} - -bool BaseClientPrivate::connectToHostAndWait(std::string hostname, unsigned short port) -{ - if (hostname == "localhost:") - { - hostname = "localhost:/tmp/hydrogenserver"; - } - clientSocket.connectToHost(hostname, port); - return clientSocket.waitForConnected(timeout_sec * 1000 + timeout_us / 1000); -} - -bool BaseClient::connectServer() -{ - D_PTR(BaseClient); - - if (d->sConnected == true) - { - IDLog("HYDROGEN::BaseClient::connectServer: Already connected.\n"); - return false; - } - - IDLog("HYDROGEN::BaseClient::connectServer: creating new connection...\n"); - -#ifndef _WINDOWS - // System with unix support automatically connect over unix domain - if (d->cServer != "localhost" || d->cServer != "127.0.0.1" || d->connectToHostAndWait("localhost:", d->cPort) == false) -#endif - { - if (d->connectToHostAndWait(d->cServer, d->cPort) == false) - { - d->sConnected = false; - return false; - } - } - - d->clear(); - - d->sConnected = true; - - serverConnected(); - - d->userIoGetProperties(); - - return true; -} - -bool BaseClient::disconnectServer(int exit_code) -{ - D_PTR(BaseClient); - - if (d->sConnected.exchange(false) == false) - { - IDLog("HYDROGEN::BaseClient::disconnectServer: Already disconnected.\n"); - return false; - } - - d->clientSocket.disconnectFromHost(); - bool ret = d->clientSocket.waitForDisconnected(); - // same behavior as in `BaseClientQt::disconnectServer` - serverDisconnected(exit_code); - return ret; -} - -void BaseClient::enableDirectBlobAccess(const char * dev, const char * prop) -{ -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - D_PTR(BaseClient); - d->clientSocket.sharedBlobs.enableDirectBlobAccess(dev, prop); -#else - HYDROGEN_UNUSED(dev); - HYDROGEN_UNUSED(prop); -#endif -} - -} diff --git a/modules/hydrogen_client/baseclient.h b/modules/hydrogen_client/baseclient.h deleted file mode 100644 index 8c94bd3f..00000000 --- a/modules/hydrogen_client/baseclient.h +++ /dev/null @@ -1,84 +0,0 @@ -/******************************************************************************* - Copyright(c) 2011 Jasem Mutlaq. All rights reserved. - 2022 Ludovic Pollet - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#pragma once - -#ifndef SWIG -#include "abstractbaseclient.h" -#else -%include "abstractbaseclient.h" -#endif - -/** @class HYDROGEN::BaseClient - * @brief Class to provide basic client functionality. - * - * BaseClient enables accelerated development of HYDROGEN Clients by providing a framework that facilitates communication, device - * handling, and event notification. By subclassing BaseClient, clients can quickly connect to an HYDROGEN server, and query for - * a set of HYDROGEN::BaseDevice devices, and read and write properties seamlessly. Event driven programming is possible due to - * notifications upon reception of new devices or properties. - * - * Upon connecting to an HYDROGEN server, it creates a dedicated thread to handle all incoming traffic. The thread is terminated - * when disconnectServer() is called or when a communication error occurs. - * - * @attention All notifications functions defined in HYDROGEN::BaseMediator must be implemented in the client class even if - * they are not used because these are pure virtual functions. - * - * @see HYDROGEN Client Tutorial for more details. - * @author Jasem Mutlaq - * @author Ludovic Pollet - */ - -namespace HYDROGEN -{ -class BaseClientPrivate; -} -class HYDROGEN::BaseClient : public HYDROGEN::AbstractBaseClient -{ - DECLARE_PRIVATE_D(d_ptr_hydrogen, BaseClient) - - public: - BaseClient(); - virtual ~BaseClient(); - - public: - /** @brief Connect to HYDROGEN server. - * @returns True if the connection is successful, false otherwise. - * @note This function blocks until connection is either successull or unsuccessful. - */ - bool connectServer() override; - - /** @brief Disconnect from HYDROGEN server. - * Any devices previously created will be deleted and memory cleared. - * @return True if disconnection is successful, false otherwise. - */ - bool disconnectServer(int exit_code = 0) override; - - public: - /** @brief activate zero-copy delivering of the blob content. - * When enabled, all blob copy will be avoided when possible (depending on the connection). - * This changes how the IBLOB.data field : - *
    - *
  • it will point to readonly data: The client must not try to modify its content or realloc it
  • - *
  • when freeing is required, the function IDSharedBlobFree must be used instead of free/realloc.
  • - *
- * @param dev name of device, can be NULL to all devs - * @param prop property name, can be NULL to activate for all property of dev - */ - void enableDirectBlobAccess(const char * dev = nullptr, const char * prop = nullptr); -}; diff --git a/modules/hydrogen_client/baseclient_p.h b/modules/hydrogen_client/baseclient_p.h deleted file mode 100644 index 45a8b18d..00000000 --- a/modules/hydrogen_client/baseclient_p.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include "abstractbaseclient_p.h" -#include "hydrogenlilxml.hpp" - -#include - -namespace HYDROGEN -{ - -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY -class ClientSharedBlobs -{ - public: - class Blobs : public std::vector - { - public: - ~Blobs(); - }; - - public: - void enableDirectBlobAccess(const char * dev, const char * prop); - void disableDirectBlobAccess(); - - bool parseAttachedBlobs(const HYDROGEN::LilXmlElement &root, Blobs &blobs); - bool isDirectBlobAccess(const std::string &dev, const std::string &prop) const; - - static bool hasDirectBlobAccessEntry(const std::map> &directBlobAccess, - const std::string &dev, const std::string &prop); - - void addIncomingSharedBuffer(int fd); - - void clear(); - - private: - std::list incomingSharedBuffers; - std::map> directBlobAccess; -}; - -class TcpSocketSharedBlobs : public TcpSocket -{ - public: - void readyRead() override; - - ClientSharedBlobs sharedBlobs; -}; -#endif - -class BaseDevice; - - -class BaseClientPrivate : public AbstractBaseClientPrivate -{ - public: - BaseClientPrivate(BaseClient *parent); - virtual ~BaseClientPrivate(); - - public: - bool connectToHostAndWait(std::string hostname, unsigned short port); - - public: - ssize_t sendData(const void *data, size_t size) override; - -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - TcpSocketSharedBlobs clientSocket; -#else - TcpSocket clientSocket; -#endif - LilXmlParser xmlParser; -}; - -} diff --git a/modules/hydrogen_client/baseclientqt.cpp b/modules/hydrogen_client/baseclientqt.cpp deleted file mode 100644 index 59f95bb4..00000000 --- a/modules/hydrogen_client/baseclientqt.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/******************************************************************************* - Copyright(c) 2016 Jasem Mutlaq. All rights reserved. - - HYDROGEN Qt Client - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#include "baseclientqt.h" -#include "baseclientqt_p.h" - -#include "abstractbaseclient.h" -#include "abstractbaseclient_p.h" -namespace HYDROGEN -{ - -// BaseClientQtPrivate - -BaseClientQtPrivate::BaseClientQtPrivate(BaseClientQt *parent) - : AbstractBaseClientPrivate(parent) -{ } - -ssize_t BaseClientQtPrivate::sendData(const void *data, size_t size) -{ - return clientSocket.write(static_cast(data), size); -} - -void BaseClientQtPrivate::listenHYDROGEN() -{ - char msg[MAXRBUF]; - - if (sConnected == false) - return; - - while (clientSocket.bytesAvailable() > 0) - { - const QByteArray data = clientSocket.readAll(); - - auto documents = xmlParser.parseChunk(data.constData(), data.size()); - - if (documents.size() == 0) - { - if (xmlParser.hasErrorMessage()) - { - IDLog("Bad XML from %s/%d: %s\n%.*s\n", cServer.c_str(), cPort, xmlParser.errorMessage(), data.size(), data.constData()); - } - break; - } - - for (const auto &doc: documents) - { - LilXmlElement root = doc.root(); - - if (verbose) - root.print(stderr, 0); - - int err_code = dispatchCommand(root, msg); - - if (err_code < 0) - { - // Silenty ignore property duplication errors - if (err_code != HYDROGEN_PROPERTY_DUPLICATED) - { - IDLog("Dispatch command error(%d): %s\n", err_code, msg); - root.print(stderr, 0); - } - } - } - } -} - -// BaseClientQt - -BaseClientQt::BaseClientQt(QObject *parent) - : QObject(parent) - , AbstractBaseClient(std::unique_ptr(new BaseClientQtPrivate(this))) -{ - D_PTR(BaseClientQt); - connect(&d->clientSocket, &QTcpSocket::readyRead, this, [d]() - { - d->listenHYDROGEN(); - }); - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) - connect(&d->clientSocket, &QTcpSocket::errorOccurred, this, [d, this](QAbstractSocket::SocketError socketError) -#else - connect(&d->clientSocket, qOverload(&QTcpSocket::error), this, [d, this](QAbstractSocket::SocketError socketError) -#endif - { - if (d->sConnected == false) - return; - - // TODO Handle what happens on socket failure! - HYDROGEN_UNUSED(socketError); - IDLog("Socket Error: %s\n", d->clientSocket.errorString().toLatin1().constData()); - fprintf(stderr, "HYDROGEN server %s/%d disconnected.\n", d->cServer.c_str(), d->cPort); - d->clientSocket.close(); - // Let client handle server disconnection - serverDisconnected(-1); - }); -} - -BaseClientQt::~BaseClientQt() -{ - D_PTR(BaseClientQt); - d->clear(); -} - -bool BaseClientQt::connectServer() -{ - D_PTR(BaseClientQt); - d->clientSocket.connectToHost(d->cServer.c_str(), d->cPort); - - if (d->clientSocket.waitForConnected(d->timeout_sec * 1000) == false) - { - d->sConnected = false; - return false; - } - - d->clear(); - - d->sConnected = true; - - serverConnected(); - - d->userIoGetProperties(); - - return true; -} - -bool BaseClientQt::disconnectServer(int exit_code) -{ - D_PTR(BaseClientQt); - - if (d->sConnected == false) - return true; - - d->sConnected = false; - - d->clientSocket.close(); - - d->clear(); - - d->watchDevice.unwatchDevices(); - - serverDisconnected(exit_code); - - return true; -} - -} diff --git a/modules/hydrogen_client/baseclientqt.h b/modules/hydrogen_client/baseclientqt.h deleted file mode 100644 index c64c8793..00000000 --- a/modules/hydrogen_client/baseclientqt.h +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************* - Copyright(c) 2016 Jasem Mutlaq. All rights reserved. - - HYDROGEN Qt Client - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#pragma once - -#include "abstractbaseclient.h" - -#include - -/** - * @class HYDROGEN::BaseClientQt - @brief Class to provide basic client functionality based on Qt5 toolkit and is therefore suitable for cross-platform development. - - BaseClientQt enables accelerated development of HYDROGEN Clients by providing a framework that facilitates communication, device - handling, and event notification. By subclassing BaseClientQt, clients can quickly connect to an HYDROGEN server, and query for - a set of HYDROGEN::BaseDevice devices, and read and write properties seamlessly. Event driven programming is possible due to - notifications upon reception of new devices or properties. - - @attention All notifications functions defined in HYDROGEN::BaseMediator must be implemented in the client class even if - they are not used because these are pure virtual functions. - - @see HYDROGEN Client Tutorial for more details. - @author Jasem Mutlaq - - */ -namespace HYDROGEN -{ -class BaseClientQtPrivate; -} -class HYDROGEN::BaseClientQt : public QObject, public HYDROGEN::AbstractBaseClient -{ - Q_OBJECT - DECLARE_PRIVATE_D(d_ptr_hydrogen, BaseClientQt) - - public: - BaseClientQt(QObject *parent = Q_NULLPTR); - virtual ~BaseClientQt(); - - public: - /** @brief Connect to HYDROGEN server. - * @returns True if the connection is successful, false otherwise. - * @note This function blocks until connection is either successull or unsuccessful. - */ - bool connectServer() override; - - /** @brief Disconnect from HYDROGEN server. - * Any devices previously created will be deleted and memory cleared. - * @return True if disconnection is successful, false otherwise. - */ - bool disconnectServer(int exit_code = 0) override; - - private: - void enableDirectBlobAccess(const char * dev = nullptr, const char * prop = nullptr) = delete; // not implemented -}; diff --git a/modules/hydrogen_client/baseclientqt_p.h b/modules/hydrogen_client/baseclientqt_p.h deleted file mode 100644 index d0b4f1ed..00000000 --- a/modules/hydrogen_client/baseclientqt_p.h +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* - Copyright(c) 2016 Jasem Mutlaq. All rights reserved. - Copyright(c) 2022 Pawel Soja - - HYDROGEN Qt Client - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ -#pragma once - -#include "abstractbaseclient_p.h" -#include "hydrogenlilxml.h" - -#include - -namespace HYDROGEN -{ - -class BaseClientQtPrivate : public AbstractBaseClientPrivate -{ - public: - BaseClientQtPrivate(BaseClientQt *parent); - ~BaseClientQtPrivate() = default; - - public: - ssize_t sendData(const void *data, size_t size) override; - - public: - void listenHYDROGEN(); - - public: - QTcpSocket clientSocket; - LilXmlParser xmlParser; - -}; -} diff --git a/modules/hydrogen_client/socket/CMakeLists.txt b/modules/hydrogen_client/socket/CMakeLists.txt deleted file mode 100644 index 3818f533..00000000 --- a/modules/hydrogen_client/socket/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -cmake_minimum_required(VERSION 3.13) -project(sockets CXX) - -find_package(Threads REQUIRED) -add_library(${PROJECT_NAME} OBJECT "") - -# Headers -list(APPEND ${PROJECT_NAME}_HEADERS - tcpsocket.h -) - -list(APPEND ${PROJECT_NAME}_PRIVATE_HEADERS - tcpsocket_p.h -) - -# Sources -list(APPEND ${PROJECT_NAME}_SOURCES - tcpsocket.cpp -) - -if(UNIX) - list(APPEND ${PROJECT_NAME}_SOURCES - tcpsocket_unix.cpp - ) -else() - list(APPEND ${PROJECT_NAME}_SOURCES - tcpsocket_win.cpp - ) - target_link_libraries(${PROJECT_NAME} -lws2_32) -endif() - -# Setup Target -target_sources(${PROJECT_NAME} - PUBLIC - ${${PROJECT_NAME}_HEADERS} - PRIVATE - ${${PROJECT_NAME}_SOURCES} - ${${PROJECT_NAME}_PRIVATE_HEADERS} -) - -target_include_directories(${PROJECT_NAME} - PUBLIC ./ -) - -target_link_libraries(${PROJECT_NAME} Threads::Threads) diff --git a/modules/hydrogen_client/socket/select.h b/modules/hydrogen_client/socket/select.h deleted file mode 100644 index 30fbd5bd..00000000 --- a/modules/hydrogen_client/socket/select.h +++ /dev/null @@ -1,233 +0,0 @@ -/* - Copyright (C) 2022 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -// internal use only -#pragma once - -#include -#include - -#ifdef _WIN32 -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#include -#include -typedef SOCKET SocketFileDescriptor; -static const SocketFileDescriptor SocketInvalid = INVALID_SOCKET; -#else -#define HAS_EVENT_FD -#include -#include -#include // select -typedef int SocketFileDescriptor; -static const SocketFileDescriptor SocketInvalid = -1; -#endif - -#include "util/macro.hpp" - -#ifdef HAS_EVENT_FD -class EventFd -{ - public: - EventFd() - { - if (socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd) < 0) - { - perror("socketpair"); - } - } - - ~EventFd() - { - close(pipefd[0]); - close(pipefd[1]); - } - - public: - void wakeUp() - { - size_t c = 1; - // wakeup 'select' function - ssize_t ret = write(pipefd[1], &c, sizeof(c)); - - if (ret != sizeof(c)) - { - perror("the socket cannot be woken up"); - } - total += ret; - } - - int fd() const - { - return pipefd[0]; - } - - void clear() - { - size_t c = 0; - while (total > 0) - { - total -= read(pipefd[0], &c, sizeof(c)); - } - } - - private: - int pipefd[2] = {-1, -1}; - int total = 0; -}; -#endif - -// wrapper for unix select function -class Select -{ - public: - Select() - { - clear(); - } - - public: - void wakeUp() - { -#ifdef HAS_EVENT_FD - eventFd.wakeUp(); -#endif - } - - void clear() - { - FD_ZERO(&readEvent); - FD_ZERO(&writeEvent); - FD_ZERO(&exceptionEvent); - fdMax = 0; -#ifdef HAS_EVENT_FD - eventFd.clear(); -#endif - } - - public: - void setTimeout(int timeout) - { - ts.tv_sec = timeout / 1000; - ts.tv_usec = (timeout % 1000) * 1000; - } - - void select() - { -#ifdef HAS_EVENT_FD - setReadEvent(eventFd.fd()); -#endif - readyDesc = ::select(int(fdMax) + 1, &readEvent, &writeEvent, &exceptionEvent, &ts); -#ifdef HAS_EVENT_FD - if (isReadEvent(eventFd.fd())) - { - eventFd.clear(); - } -#endif - } - - void select(int timeout) - { - setTimeout(timeout); - select(); - } - - public: - void setReadEvent(SocketFileDescriptor fd) - { - FD_SET(fd, &readEvent); - fdMax = std::max(fdMax, fd); - } - - void setWriteEvent(SocketFileDescriptor fd) - { - FD_SET(fd, &writeEvent); - fdMax = std::max(fdMax, fd); - } - - void setExceptionEvent(SocketFileDescriptor fd) - { - FD_SET(fd, &exceptionEvent); - fdMax = std::max(fdMax, fd); - } - - void setReadWriteEvent(SocketFileDescriptor fd) - { - FD_SET(fd, &readEvent); - FD_SET(fd, &writeEvent); - fdMax = std::max(fdMax, fd); - } - - void setReadWriteExceptionEvent(SocketFileDescriptor fd) - { - FD_SET(fd, &readEvent); - FD_SET(fd, &writeEvent); - FD_SET(fd, &exceptionEvent); - fdMax = std::max(fdMax, fd); - } - - public: -#ifdef HAS_EVENT_FD - bool isWakedUp() const - { - return FD_ISSET(eventFd.fd(), &readEvent); - } -#else - bool isWakedUp() const - { - return false; - } -#endif - bool isTimeout() const - { - return readyDesc == 0; - } - bool isError() const - { - return readyDesc < 0; - } - bool isReadEvent(SocketFileDescriptor fd) const - { - return FD_ISSET(fd, &readEvent); - } - bool isWriteEvent(SocketFileDescriptor fd) const - { - return FD_ISSET(fd, &writeEvent); - } - bool isExceptionEvent(SocketFileDescriptor fd) const - { - return FD_ISSET(fd, &exceptionEvent); - } - - protected: - fd_set readEvent; - fd_set writeEvent; - fd_set exceptionEvent; - SocketFileDescriptor fdMax {0}; - int readyDesc {0}; - - struct timeval ts - { - 1, 0 - }; - -#ifdef HAS_EVENT_FD - EventFd eventFd; -#endif -}; diff --git a/modules/hydrogen_client/socket/tcpsocket.cpp b/modules/hydrogen_client/socket/tcpsocket.cpp deleted file mode 100644 index e81828f4..00000000 --- a/modules/hydrogen_client/socket/tcpsocket.cpp +++ /dev/null @@ -1,541 +0,0 @@ -/* - Copyright (C) 2022 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "tcpsocket.h" -#include "tcpsocket_p.h" - -#include -#include -#include - -// SocketAddress -const char *SocketAddress::unixDomainPrefix = "localhost:"; - -SocketAddress::SocketAddress(const std::string &hostName, unsigned short port) -{ - if (isUnix(hostName)) - *this = SocketAddress::afUnix(hostName.substr(strlen(unixDomainPrefix))); - else - *this = SocketAddress::afInet(hostName, port); -} - -SocketAddress SocketAddress::afInet(const std::string &hostName, unsigned short port) -{ - struct hostent *hp = gethostbyname(hostName.c_str()); - if (hp == nullptr) - return SocketAddress(); - - if (hp->h_addr_list == nullptr) - return SocketAddress(); - - if (hp->h_addr_list[0] == nullptr) - return SocketAddress(); - - struct sockaddr_in *sa_in = new sockaddr_in; - (void)memset(sa_in, 0, sizeof(struct sockaddr_in)); - sa_in->sin_family = AF_INET; - sa_in->sin_addr.s_addr = ((struct in_addr *)(hp->h_addr_list[0]))->s_addr; - sa_in->sin_port = htons(port); - - SocketAddress result; - result.mData.reset(reinterpret_cast(sa_in)); - result.mSize = sizeof(struct sockaddr_in); - return result; -} - -bool SocketAddress::isUnix(const std::string &hostName) -{ - return hostName.rfind(unixDomainPrefix, 0) == 0; -} - -// TcpSocketPrivate -TcpSocketPrivate::TcpSocketPrivate(TcpSocket *parent) - : parent(parent) -{ } - -TcpSocket::TcpSocket(std::unique_ptr &&d) - : d_ptr(std::move(d)) -{ } - -ssize_t TcpSocketPrivate::write(const void *data, size_t size) -{ - ssize_t ret; - do - { - std::unique_lock locker(socketStateMutex); - if (socketState != TcpSocket::ConnectedState) - { - return 0; - } - ret = TcpSocketPrivate::sendSocket(data, size); - } - while (ret == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); - - if (ret < 0) - { - setSocketError(TcpSocket::ConnectionRefusedError); - return 0; - } - - return ret; -} - -bool TcpSocketPrivate::connectSocket(const std::string &hostName, unsigned short port) -{ - // create socket handle - if (!createSocket(SocketAddress::isUnix(hostName) ? AF_UNIX : AF_INET)) - { - setSocketError(TcpSocket::SocketResourceError); - return false; - } - - // set non blocking mode - if (!setNonblockSocket()) - { - setSocketError(TcpSocket::UnknownSocketError); - return false; - } - - // get socket address - auto sockAddr = SocketAddress(hostName, port); - - if (!sockAddr.isValid()) - { - setSocketError(TcpSocket::HostNotFoundError); - return false; - } - - // connect to host - if (::connect(socketFd, &sockAddr, int(sockAddr.size())) < 0 && errno != EINPROGRESS) - { - setSocketError(TcpSocket::UnknownSocketError); - return false; - } - - return true; -} - -bool TcpSocketPrivate::waitForConnectedSockets() -{ - // wait for connect - select.clear(); - select.setReadWriteExceptionEvent(socketFd); - select.select(timeout); - - if (select.isTimeout()) - { - setSocketError(TcpSocket::SocketTimeoutError); - return false; - } - - if (select.isWakedUp()) - { - return false; - } - - return TcpSocketPrivate::sendSocket("", 0) == 0; // final test, -1 if not connected -} - -bool TcpSocketPrivate::processSocket() -{ - select.clear(); - select.setReadEvent(socketFd); -#ifndef _WIN32 - select.setTimeout(10 * 1000); // we can wake up -#else - select.setTimeout(100); -#endif - - select.select(); - - // timeout, continue... - if (select.isTimeout()) - { - return true; - } - - // manual wakeup, maybe isAboutToClose, exit - if (select.isWakedUp()) - { - return true; - } - - // nothing to do - if (!select.isReadEvent(socketFd)) - { - return true; - } - - // call virtual method - parent->readyRead(); - - return true; -} - -void TcpSocketPrivate::joinThread(std::thread &thread) -{ - std::unique_lock locker(socketStateMutex); - isAboutToClose = true; - if (thread.joinable()) - { - thread.join(); - } - isAboutToClose = false; -} - -class Finally -{ - typedef std::function F; - F f; - public: - Finally(const F &f) : f(f) { } - ~Finally() - { - if (f) f(); - } -}; - -void TcpSocketPrivate::connectToHost(const std::string &hostName, unsigned short port) -{ - if (socketState != TcpSocket::UnconnectedState) - { - setSocketError(TcpSocket::OperationError); - return; - } - - setSocketState(TcpSocket::HostLookupState); - - thread = std::thread([this, hostName, port] (std::thread && oldThread) - { - Finally finally([this] - { - closeSocket(); - setSocketState(TcpSocket::UnconnectedState); - }); - - joinThread(oldThread); // join prevus thread if exists - - // lookup and connect - if (!connectSocket(hostName, port)) - { - // see error in connectSocket - return; - } - // wait for connected - setSocketState(TcpSocket::ConnectingState); - if (!waitForConnectedSockets()) - { - setSocketError(TcpSocket::SocketError::HostNotFoundError); - return; - } - - setSocketState(TcpSocket::ConnectedState); - parent->connected(); - while (isAboutToClose == false && processSocket()); - parent->disconnected(); - - }, std::move(thread)); -} - -void TcpSocketPrivate::aboutToClose() -{ - std::unique_lock locker(socketStateMutex); - if (socketState == TcpSocket::UnconnectedState || isAboutToClose.exchange(true) == true) - { - return; - } - select.wakeUp(); -} - -void TcpSocketPrivate::closeSocket() -{ - if (socketFd == SocketInvalid) - return; - -#ifdef _WIN32 - ::closesocket(socketFd); -#else - ::close(socketFd); -#endif - socketFd = SocketInvalid; -} - -void TcpSocketPrivate::setSocketError(TcpSocket::SocketError error, ErrorType errorType, const std::string &errorString) -{ - if (errorType == ErrorTypeSystem && errorString == "") - { -#ifdef _WIN32 - LPTSTR s = nullptr; - auto code = WSAGetLastError(); - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, code, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&s, 0, nullptr - ); - -#ifdef UNICODE - std::wstring ws(s); - this->errorString = std::string(ws.begin(), ws.end()); -#else - this->errorString = s; -#endif - LocalFree(s); - - this->errorString += " (" + std::to_string(code) + ")"; - -#else - this->errorString = strerror(errno); - this->errorString += " (" + std::to_string(errno) + ")"; -#endif - } - else - { - this->errorString = errorString; - } - socketError = error; - isAboutToClose = true; - parent->errorOccurred(error); -} - -void TcpSocketPrivate::setSocketState(TcpSocket::SocketState state) -{ - std::unique_lock locker(socketStateMutex); - if (socketState.exchange(state) == state) - return; - socketStateChanged.notify_all(); -} - -// TcpSocket -TcpSocket::TcpSocket() - : d_ptr(new TcpSocketPrivate(this)) -{ } - -TcpSocket::~TcpSocket() -{ - disconnectFromHost(); - if (waitForDisconnected()) - { - d_ptr->joinThread(d_ptr->thread); - } -} - -void TcpSocket::setConnectionTimeout(int timeout) -{ - d_ptr->timeout = timeout; -} - -void TcpSocket::connectToHost(const std::string &hostName, uint16_t port) -{ - d_ptr->connectToHost(hostName, port); -} - -void TcpSocket::disconnectFromHost() -{ - d_ptr->aboutToClose(); -} - -ssize_t TcpSocket::write(const char *data, size_t size) -{ - return d_ptr->write(data, size); -} - -ssize_t TcpSocket::write(const std::string &data) -{ - return write(data.data(), data.size()); -} - -static std::string sSocketErrorToString(TcpSocket::SocketError error) -{ - switch (error) - { - case TcpSocket::ConnectionRefusedError: - return "ConnectionRefusedError"; - case TcpSocket::RemoteHostClosedError: - return "RemoteHostClosedError"; - case TcpSocket::HostNotFoundError: - return "HostNotFoundError"; - case TcpSocket::SocketAccessError: - return "SocketAccessError"; - case TcpSocket::SocketResourceError: - return "SocketResourceError"; - case TcpSocket::SocketTimeoutError: - return "SocketTimeoutError"; - case TcpSocket::DatagramTooLargeError: - return "DatagramTooLargeError"; - case TcpSocket::NetworkError: - return "NetworkError"; - case TcpSocket::AddressInUseError: - return "AddressInUseError"; - case TcpSocket::SocketAddressNotAvailableError: - return "SocketAddressNotAvailableError"; - case TcpSocket::UnsupportedSocketOperationError: - return "UnsupportedSocketOperationError"; - case TcpSocket::UnfinishedSocketOperationError: - return "UnfinishedSocketOperationError"; - case TcpSocket::ProxyAuthenticationRequiredError: - return "ProxyAuthenticationRequiredError"; - case TcpSocket::SslHandshakeFailedError: - return "SslHandshakeFailedError"; - case TcpSocket::ProxyConnectionRefusedError: - return "ProxyConnectionRefusedError"; - case TcpSocket::ProxyConnectionClosedError: - return "ProxyConnectionClosedError"; - case TcpSocket::ProxyConnectionTimeoutError: - return "ProxyConnectionTimeoutError"; - case TcpSocket::ProxyNotFoundError: - return "ProxyNotFoundError"; - case TcpSocket::ProxyProtocolError: - return "ProxyProtocolError"; - case TcpSocket::OperationError: - return "OperationError"; - case TcpSocket::SslInternalError: - return "SslInternalError"; - case TcpSocket::SslInvalidUserDataError: - return "SslInvalidUserDataError"; - case TcpSocket::TemporaryError: - return "TemporaryError"; - case TcpSocket::UnknownSocketError: - return "UnknownSocketError"; - } - return "UnknownSocketError"; -} - -TcpSocket::SocketError TcpSocket::error() const -{ - return d_ptr->socketError; -} - -std::string TcpSocket::errorString() const -{ - return sSocketErrorToString(d_ptr->socketError) + ": " + d_ptr->errorString; -} - -void TcpSocket::onConnected(const std::function &callback) -{ - d_ptr->onConnected = callback; -} - -void TcpSocket::onDisconnected(const std::function &callback) -{ - d_ptr->onDisconnected = callback; -} - -void TcpSocket::onData(const std::function &callback) -{ - d_ptr->onData = callback; -} - -void TcpSocket::onErrorOccurred(const std::function &callback) -{ - d_ptr->onErrorOccurred = callback; -} - -bool TcpSocket::waitForConnected(int timeout) const -{ - if (d_ptr->thread.get_id() == std::this_thread::get_id()) - { - d_ptr->setSocketError(TcpSocket::SocketError::OperationError); - return false; - } - - std::unique_lock locker(d_ptr->socketStateMutex); - d_ptr->socketStateChanged.wait_for(locker, std::chrono::milliseconds(timeout), [this] - { - return d_ptr->socketState == TcpSocket::ConnectedState || d_ptr->socketState == TcpSocket::UnconnectedState; - }); - return d_ptr->socketState == TcpSocket::ConnectedState; -} - -bool TcpSocket::waitForDisconnected(int timeout) const -{ - if (d_ptr->thread.get_id() == std::this_thread::get_id()) - { - d_ptr->setSocketError(TcpSocket::SocketError::OperationError); - return false; - } - - std::unique_lock locker(d_ptr->socketStateMutex); - return d_ptr->socketStateChanged.wait_for(locker, std::chrono::milliseconds(timeout), [this] - { - return d_ptr->socketState == TcpSocket::UnconnectedState; - }); - -} - -int *TcpSocket::socketDescriptor() const -{ - return reinterpret_cast(d_ptr->socketFd); -} - -void TcpSocket::connected() -{ - emitConnected(); -} - -void TcpSocket::disconnected() -{ - emitDisconnected(); -} - -void TcpSocket::readyRead() -{ - char data[65536]; - ssize_t size = d_ptr->recvSocket(data, sizeof(data)); - - if (size <= 0) - { - setSocketError(TcpSocket::ConnectionRefusedError); - return; - } - - emitData(data, size); -} - -void TcpSocket::errorOccurred(SocketError error) -{ - emitErrorOccurred(error); -} - -void TcpSocket::emitConnected() const -{ - if (d_ptr->onConnected) - d_ptr->onConnected(); -} - -void TcpSocket::emitDisconnected() const -{ - if (d_ptr->onDisconnected) - d_ptr->onDisconnected(); -} - -void TcpSocket::emitData(const char *data, size_t size) const -{ - if (d_ptr->onData) - d_ptr->onData(data, size); -} - -void TcpSocket::emitErrorOccurred(SocketError error) const -{ - if (d_ptr->onErrorOccurred) - d_ptr->onErrorOccurred(error); -} - -void TcpSocket::setSocketError(SocketError socketError) -{ - d_ptr->setSocketError(socketError); -} diff --git a/modules/hydrogen_client/socket/tcpsocket.h b/modules/hydrogen_client/socket/tcpsocket.h deleted file mode 100644 index 60af18f9..00000000 --- a/modules/hydrogen_client/socket/tcpsocket.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - Copyright (C) 2022 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#pragma once - -#include -#include -#include - -#include "util/macro.hpp" - -class TcpSocketPrivate; -class TcpSocket -{ - public: - enum SocketError - { - ConnectionRefusedError, - RemoteHostClosedError, - HostNotFoundError, - SocketAccessError, - SocketResourceError, - SocketTimeoutError, /* 5 */ - DatagramTooLargeError, - NetworkError, - AddressInUseError, - SocketAddressNotAvailableError, - UnsupportedSocketOperationError, /* 10 */ - UnfinishedSocketOperationError, - ProxyAuthenticationRequiredError, - SslHandshakeFailedError, - ProxyConnectionRefusedError, - ProxyConnectionClosedError, /* 15 */ - ProxyConnectionTimeoutError, - ProxyNotFoundError, - ProxyProtocolError, - OperationError, - SslInternalError, /* 20 */ - SslInvalidUserDataError, - TemporaryError, - - UnknownSocketError = -1 - }; - - enum SocketState - { - UnconnectedState, - HostLookupState, - ConnectingState, - ConnectedState, - BoundState, - ListeningState, - ClosingState - }; - - public: - TcpSocket(); - virtual ~TcpSocket(); - - public: - void setConnectionTimeout(int timeout); - - public: - void connectToHost(const std::string &hostName, uint16_t port); - void disconnectFromHost(); - - public: - ssize_t write(const char *data, size_t size); - ssize_t write(const std::string &data); - - public: - SocketError error() const; - std::string errorString() const; - - public: - void onConnected(const std::function &callback); - void onDisconnected(const std::function &callback); - void onData(const std::function &callback); - void onErrorOccurred(const std::function &callback); - - public: - bool waitForDisconnected(int timeout = 2000) const; - bool waitForConnected(int timeout = 2000) const; - - public: - int *socketDescriptor() const; - - protected: - virtual void connected(); - virtual void disconnected(); - virtual void readyRead(); - virtual void errorOccurred(SocketError); - - protected: - void emitConnected() const; - void emitDisconnected() const; - void emitData(const char *data, size_t size) const; - void emitErrorOccurred(SocketError error) const; - - protected: - void setSocketError(SocketError socketError); - - protected: - friend class TcpSocketPrivate; - std::unique_ptr d_ptr; - TcpSocket(std::unique_ptr &&d); -}; diff --git a/modules/hydrogen_client/socket/tcpsocket_p.h b/modules/hydrogen_client/socket/tcpsocket_p.h deleted file mode 100644 index a3ee0a34..00000000 --- a/modules/hydrogen_client/socket/tcpsocket_p.h +++ /dev/null @@ -1,134 +0,0 @@ -/* - Copyright (C) 2022 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#pragma once - -#include "tcpsocket.h" -#include "select.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -class SocketAddress -{ - public: - static const char *unixDomainPrefix; - - public: - SocketAddress() = default; - explicit SocketAddress(const std::string &hostName, unsigned short port); - - public: - static bool isUnix(const std::string &hostName); - - public: - bool isValid() const - { - return data() != nullptr; - } - - const struct sockaddr *data() const - { - return mData.get(); - } - - size_t size() const - { - return mSize; - } - - public: - const struct sockaddr *operator&() const - { - return data(); - } - - protected: - static SocketAddress afInet(const std::string &hostName, unsigned short port); - static SocketAddress afUnix(const std::string &hostName); - - protected: - std::unique_ptr mData; - size_t mSize; -}; - -class TcpSocketPrivate -{ - public: - TcpSocketPrivate(TcpSocket *parent); - virtual ~TcpSocketPrivate() = default; - - public: // platform dependent - bool createSocket(int domain); - void closeSocket(); - ssize_t recvSocket(void *dst, size_t size); - ssize_t sendSocket(const void *src, size_t size); - bool setNonblockSocket(); - - public: // low level helpers - bool connectSocket(const std::string &hostName, unsigned short port); - bool waitForConnectedSockets(); - bool processSocket(); - - public: // TcpSocketPrivate API - ssize_t write(const void *data, size_t size); - - void connectToHost(const std::string &hostName, unsigned short port); - void aboutToClose(); - - void joinThread(std::thread &thread); - - public: - enum ErrorType - { - ErrorTypeSystem, - ErrorTypeInternal - }; - void setSocketError(TcpSocket::SocketError error, ErrorType errorType = ErrorTypeSystem, - const std::string &errorString = ""); - void setSocketState(TcpSocket::SocketState state); - - public: - TcpSocket *parent; - SocketFileDescriptor socketFd = SocketInvalid; - Select select; - int timeout{30000}; - - std::thread thread; - std::atomic isAboutToClose{false}; - - mutable std::mutex socketStateMutex; - mutable std::condition_variable socketStateChanged; - - std::atomic socketState{TcpSocket::UnconnectedState}; - TcpSocket::SocketError socketError; - std::string errorString; - - // events - std::function onConnected; - std::function onDisconnected; - std::function onData; - std::function onErrorOccurred; -}; diff --git a/modules/hydrogen_client/socket/tcpsocket_unix.cpp b/modules/hydrogen_client/socket/tcpsocket_unix.cpp deleted file mode 100644 index ac23b685..00000000 --- a/modules/hydrogen_client/socket/tcpsocket_unix.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright (C) 2022 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "tcpsocket.h" -#include "tcpsocket_p.h" - -#include - -bool TcpSocketPrivate::createSocket(int domain) -{ - socketFd = ::socket(domain, SOCK_STREAM, 0); - return socketFd >= 0; -} - -bool TcpSocketPrivate::setNonblockSocket() -{ - int flags = fcntl(socketFd, F_GETFL, 0); - if (flags < 0) - { - return false; - } - - if (fcntl(socketFd, F_SETFL, flags | O_NONBLOCK) < 0) - { - return false; - } - - return true; -} - -ssize_t TcpSocketPrivate::recvSocket(void *dst, size_t size) -{ - return ::read(socketFd, dst, size); -} - -ssize_t TcpSocketPrivate::sendSocket(const void *src, size_t size) -{ - return ::write(socketFd, src, size); -} - -SocketAddress SocketAddress::afUnix(const std::string &unixPath) -{ - struct sockaddr_un *sa_un = new struct sockaddr_un; - - (void)memset(sa_un, 0, sizeof(struct sockaddr_un)); - sa_un->sun_family = AF_UNIX; - -#ifdef __linux__ - // Using abstract socket path to avoid filesystem boilerplate - const int offset = 1; -#else - // Using filesystem socket path - const int offset = 0; -#endif - strncpy(sa_un->sun_path + offset, unixPath.c_str(), sizeof(sa_un->sun_path) - offset - 1); - - SocketAddress result; - result.mData.reset(reinterpret_cast(sa_un)); - result.mSize = offsetof(struct sockaddr_un, sun_path) + unixPath.size() + offset; - return result; -} diff --git a/modules/hydrogen_client/socket/tcpsocket_win.cpp b/modules/hydrogen_client/socket/tcpsocket_win.cpp deleted file mode 100644 index e8f0f94d..00000000 --- a/modules/hydrogen_client/socket/tcpsocket_win.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright (C) 2022 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "tcpsocket.h" -#include "tcpsocket_p.h" - -#ifdef _MSC_VER -#pragma comment(lib, "Ws2_32.lib") -#endif - -bool TcpSocketPrivate::createSocket(int domain) -{ - WSADATA wsaData; - - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) - { - return false; - } - - socketFd = socket(domain, SOCK_STREAM, IPPROTO_TCP); - if (socketFd == INVALID_SOCKET) - { - WSACleanup(); - //IDLog("Socket error: %d\n", WSAGetLastError()); - return false; - } - return true; -} - -bool TcpSocketPrivate::setNonblockSocket() -{ - u_long iMode = 0; - int iResult = ioctlsocket(socketFd, FIONBIO, &iMode); - return iResult == NO_ERROR; -} - -ssize_t TcpSocketPrivate::recvSocket(void *dst, size_t size) -{ - return ::recv(socketFd, static_cast(dst), int(size), 0); -} - -ssize_t TcpSocketPrivate::sendSocket(const void *src, size_t size) -{ - return ::send(socketFd, static_cast(src), int(size), 0); -} - -SocketAddress SocketAddress::afUnix(const std::string &) -{ - return SocketAddress(); -} diff --git a/modules/hydrogen_loader/CMakeLists.txt b/modules/hydrogen_loader/CMakeLists.txt deleted file mode 100644 index b1c028c5..00000000 --- a/modules/hydrogen_loader/CMakeLists.txt +++ /dev/null @@ -1,57 +0,0 @@ -cmake_minimum_required(VERSION 3.13) -project(hydrogenserver CXX) - -set(server_SRC - client_info.cpp - concurrent.cpp - driver_info.cpp - fifo_server.cpp - io.cpp - message_queue.cpp - message.cpp - property.cpp - serialize.cpp - tcp_server.cpp - time.cpp - xml_util.cpp - signal.cpp - remote_driver.cpp - local_driver.cpp -) - -include_directories(${CMAKE_SOURCE_DIR}/src/core) -include_directories(${CMAKE_SOURCE_DIR}/src/core/base) -include_directories(${CMAKE_SOURCE_DIR}/src/core/io) -include_directories(${CMAKE_SOURCE_DIR}/src/core/property) -include_directories(${CMAKE_SOURCE_DIR}/src/core/timer) -include_directories(${CMAKE_SOURCE_DIR}/src/core/thread) - -if(ANDROID) - message(WARNING "HYDROGEN Server is only supported under Linux, BSD, MacOS, and Cygwin while current system is " ${CMAKE_SYSTEM_NAME}) -else() - find_package(Threads REQUIRED) - find_package(Libev REQUIRED) - find_package(UV REQUIRED) - find_package(fmt REQUIRED) - find_package(ZLIB REQUIRED) - - add_executable(${PROJECT_NAME} ${server_SRC} hydrogen_server.cpp) - - # target_compile_definitions(${PROJECT_NAME} PRIVATE USE_LIBUV=1) - target_compile_definitions(${PROJECT_NAME} PRIVATE MAIN_FUNC=1) - - target_link_libraries(hydrogenserver lithiumcorestatic hydrogencore ${CMAKE_THREAD_LIBS_INIT} ${LIBEV_LIBRARIES}) - target_include_directories(hydrogenserver SYSTEM PRIVATE ${LIBEV_INCLUDE_DIRS}) - - target_link_libraries(hydrogenserver loguru fmt ${UV_LIBRARY} backward) - - target_link_libraries(hydrogenserver ${ZLIB_LIBRARIES}) - - set_target_properties( - hydrogenserver - PROPERTIES - OUTPUT_NAME ../../../hydrogenserver - ) - - install(TARGETS hydrogenserver RUNTIME DESTINATION bin) -endif(ANDROID) diff --git a/modules/hydrogen_loader/README.md b/modules/hydrogen_loader/README.md deleted file mode 100644 index a64c10a6..00000000 --- a/modules/hydrogen_loader/README.md +++ /dev/null @@ -1,52 +0,0 @@ -Hydrogen Server - Next Generation Device Server -=============================================== - -本项目为INDI Server扩展项目,完全支持原版INDI设备,并增加了对Hydrogen设备的支持。以下是对该项目进行的主要修改和扩展: - -+ 拆分单文件:将原先的单一文件拆分成多个文件,这有助于后续的代码维护和管理。通过拆分文件,可以更好地组织代码,使得逻辑结构更清晰。 - -+ 使用最新的C++特性:在重写老旧代码的过程中,使用了部分最新的C++特性。这可能包括使用更现代化的语法和标准库特性,使代码更加简洁、高效和易读。 - -+ 增加对Windows的支持:除了原版INDI设备的支持外,Hydrogen Server还增加了对Windows操作系统的支持。这意味着可以在Windows平台上运行Hydrogen Server,并与Hydrogen设备进行交互。 - -+ 逻辑优化:对部分逻辑进行了优化,以提升程序的性能和效率。通过对代码进行改进和精简,可以改善系统的响应速度,并提供更好的用户体验。 - -## 文件目录 - -``` -. -├── CMakeLists.txt -├── README.md -├── client_info.cpp -├── client_info.hpp -├── concurrent.cpp -├── concurrent.hpp -├── driver_info.cpp -├── driver_info.hpp -├── fifo_server.cpp -├── fifo_server.hpp -├── hydrogen_server.cpp -├── hydrogen_server.hpp -├── io.cpp -├── io.hpp -├── local_driver.cpp -├── local_driver.hpp -├── message.cpp -├── message.hpp -├── message_queue.cpp -├── message_queue.hpp -├── property.cpp -├── property.hpp -├── remote_driver.cpp -├── remote_driver.hpp -├── serialize.cpp -├── serialize.hpp -├── signal.cpp -├── signal.hpp -├── tcp_server.cpp -├── tcp_server.hpp -├── time.cpp -├── time.hpp -├── xml_util.cpp -└── xml_util.hpp -``` diff --git a/modules/hydrogen_loader/client_info.cpp b/modules/hydrogen_loader/client_info.cpp deleted file mode 100644 index a23a5ef8..00000000 --- a/modules/hydrogen_loader/client_info.cpp +++ /dev/null @@ -1,314 +0,0 @@ -#include "client_info.hpp" - -#include - -#include - -#include "hydrogen_server.hpp" - -#include "property.hpp" -#include "message.hpp" -#include "driver_info.hpp" -#include "io.hpp" - -#include "atom/log/loguru.hpp" - -ClInfo::ClInfo(bool useSharedBuffer) : MsgQueue(useSharedBuffer) -{ - clients.insert(this); -} - -ClInfo::~ClInfo() -{ - for (auto prop : props) - { - delete prop; - } - - clients.erase(this); -} - -void ClInfo::log(const std::string &str) const -{ - DLOG_F(INFO, "Client %d: %s", this->getRFd(), str.c_str()); -} - -// root will be released -void ClInfo::onMessage(XMLEle *root, std::list &sharedBuffers) -{ - char *roottag = tagXMLEle(root); - - const char *dev = findXMLAttValu(root, "device"); - const char *name = findXMLAttValu(root, "name"); - int isblob = !strcmp(tagXMLEle(root), "setBLOBVector"); - - /* snag interested properties. - * N.B. don't open to alldevs if seen specific dev already, else - * remote client connections start returning too much. - */ - if (dev[0]) - { - // Signature for CHAINED SERVER - // Not a regular client. - if (dev[0] == '*' && !this->props.size()) - this->allprops = 2; - else - addDevice(dev, name, isblob); - } - else if (!strcmp(roottag, "getProperties") && !this->props.size() && this->allprops != 2) - this->allprops = 1; - - /* snag enableBLOB -- send to remote drivers too */ - if (!strcmp(roottag, "enableBLOB")) - crackBLOBHandling(dev, name, pcdataXMLEle(root)); - - if (!strcmp(roottag, "pingRequest")) - { - setXMLEleTag(root, "pingReply"); - - Msg *mp = new Msg(this, root); - pushMsg(mp); - mp->queuingDone(); - return; - } - - /* build a new message -- set content iff anyone cares */ - Msg *mp = Msg::fromXml(this, root, sharedBuffers); - if (!mp) - { - LOG_F(ERROR, "Closing after malformed message\n"); - close(); - return; - } - - /* send message to driver(s) responsible for dev */ - DvrInfo::q2RDrivers(dev, mp, root); - - /* JM 2016-05-18: Upstream client can be a chained HYDROGEN server. If any driver locally is snooping - * on any remote drivers, we should catch it and forward it to the responsible snooping driver. */ - /* send to snooping drivers. */ - // JM 2016-05-26: Only forward setXXX messages - if (!strncmp(roottag, "set", 3)) - DvrInfo::q2SDrivers(NULL, isblob, dev, name, mp, root); - - /* echo new* commands back to other clients */ - if (!strncmp(roottag, "new", 3)) - { - q2Clients(this, isblob, dev, name, mp, root); - } - - mp->queuingDone(); -} - -void ClInfo::close() -{ - if (verbose > 0) - LOG_F(ERROR, "shut down complete - bye!\n"); - - delete (this); - -#ifdef OSX_EMBEDED_MODE - fprintf(stderr, "CLIENTS %d\n", clients.size()); - fflush(stderr); -#endif -} - -void ClInfo::q2Clients(ClInfo *notme, int isblob, const std::string &dev, const std::string &name, Msg *mp, XMLEle *root) -{ - /* queue message to each interested client */ - for (auto cpId : clients.ids()) - { - auto cp = clients[cpId]; - if (cp == nullptr) - continue; - - /* cp in use? notme? want this dev/name? blob? */ - if (cp == notme) - continue; - if (cp->findDevice(dev, name) < 0) - continue; - - // if ((isblob && cp->blob==B_NEVER) || (!isblob && cp->blob==B_ONLY)) - if (!isblob && cp->blob == B_ONLY) - continue; - - if (isblob) - { - if (cp->props.size() > 0) - { - Property *blobp = nullptr; - for (auto pp : cp->props) - { - if (pp->dev == dev && pp->name == name) - { - blobp = pp; - break; - } - } - - if ((blobp && blobp->blob == B_NEVER) || (!blobp && cp->blob == B_NEVER)) - continue; - } - else if (cp->blob == B_NEVER) - continue; - } - - /* shut down this client if its q is already too large */ - unsigned long ql = cp->msgQSize(); - if (isblob && maxstreamsiz > 0 && ql > maxstreamsiz) - { - // Drop frames for streaming blobs - /* pull out each name/BLOB pair, decode */ - XMLEle *ep = NULL; - int streamFound = 0; - for (ep = nextXMLEle(root, 1); ep; ep = nextXMLEle(root, 0)) - { - if (strcmp(tagXMLEle(ep), "oneBLOB") == 0) - { - XMLAtt *fa = findXMLAtt(ep, "format"); - - if (fa && strstr(valuXMLAtt(fa), "stream")) - { - streamFound = 1; - break; - } - } - } - if (streamFound) - { - if (verbose > 1) - DLOG_F(INFO, "%ld bytes behind. Dropping stream BLOB...\n", ql); - continue; - } - } - if (ql > maxqsiz) - { - if (verbose) - DLOG_F(INFO, "%ld bytes behind, shutting down\n", ql); - cp->close(); - continue; - } - - if (verbose > 1) - DLOG_F(INFO, "queuing <%s device='%s' name='%s'>\n", - tagXMLEle(root), findXMLAttValu(root, "device"), findXMLAttValu(root, "name")); - - // pushmsg can kill cp. do at end - cp->pushMsg(mp); - } - - return; -} - -void ClInfo::q2Servers(DvrInfo *me, Msg *mp, XMLEle *root) -{ - int devFound = 0; - - /* queue message to each interested client */ - for (auto cpId : clients.ids()) - { - auto cp = clients[cpId]; - if (cp == nullptr) - continue; - - // Only send the message to the upstream server that is connected specfically to the device in driver dp - switch (cp->allprops) - { - // 0 --> not all props are requested. Check for specific combination - case 0: - for (auto pp : cp->props) - { - if (me->dev.find(pp->dev) != me->dev.end()) - { - devFound = 1; - break; - } - } - break; - - // All props are requested. This is client-only mode (not upstream server) - case 1: - break; - // Upstream server mode - case 2: - devFound = 1; - break; - } - - // If no matching device found, continue - if (devFound == 0) - continue; - - /* shut down this client if its q is already too large */ - unsigned long ql = cp->msgQSize(); - if (ql > maxqsiz) - { - if (verbose) - DLOG_F(INFO, "%ld bytes behind, shutting down\n", ql); - cp->close(); - continue; - } - - /* ok: queue message to this client */ - if (verbose > 1) - DLOG_F(INFO, "queuing <%s device='%s' name='%s'>\n", - tagXMLEle(root), findXMLAttValu(root, "device"), findXMLAttValu(root, "name")); - - // pushmsg can kill cp. do at end - cp->pushMsg(mp); - } -} - -int ClInfo::findDevice(const std::string &dev, const std::string &name) const -{ - if (allprops >= 1 || dev.empty()) - return (0); - for (auto pp : props) - { - if ((pp->dev == dev) && (pp->name.empty() || (pp->name == name))) - return (0); - } - return (-1); -} - -void ClInfo::addDevice(const std::string &dev, const std::string &name, int isblob) -{ - if (isblob) - { - for (auto pp : props) - { - if (pp->dev == dev && pp->name == name) - return; - } - } - /* no dups */ - else if (!findDevice(dev, name)) - return; - - /* add */ - Property *pp = new Property(dev, name); - props.push_back(pp); -} - -void ClInfo::crackBLOBHandling(const std::string &dev, const std::string &name, const char *enableBLOB) -{ - /* If we have EnableBLOB with property name, we add it to Client device list */ - if (!name.empty()) - addDevice(dev, name, 1); - else - /* Otherwise, we set the whole client blob handling to what's passed (enableBLOB) */ - crackBLOB(enableBLOB, &blob); - - /* If whole client blob handling policy was updated, we need to pass that also to all children - and if the request was for a specific property, then we apply the policy to it */ - for (auto pp : props) - { - if (name.empty()) - crackBLOB(enableBLOB, &pp->blob); - else if (pp->dev == dev && pp->name == name) - { - crackBLOB(enableBLOB, &pp->blob); - return; - } - } -} diff --git a/modules/hydrogen_loader/client_info.hpp b/modules/hydrogen_loader/client_info.hpp deleted file mode 100644 index c09328df..00000000 --- a/modules/hydrogen_loader/client_info.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include -#include - -#include "concurrent.hpp" -#include "message_queue.hpp" - -#include "hydrogendevapi.hpp" -#include "lilxml.hpp" - -class Msg; -class Property; -class DvrInfo; - -/* info for each connected client */ -class ClInfo : public MsgQueue -{ -protected: - /* send message to each appropriate driver. - * also send all newXXX() to all other interested clients. - */ - virtual void onMessage(XMLEle *root, std::list &sharedBuffers); - - /* Update the client property BLOB handling policy */ - void crackBLOBHandling(const std::string &dev, const std::string &name, const char *enableBLOB); - - /* close down the given client */ - virtual void close(); - -public: - std::list props; /* props we want */ - int allprops = 0; /* saw getProperties w/o device */ - BLOBHandling blob = B_NEVER; /* when to send setBLOBs */ - - ClInfo(bool useSharedBuffer); - virtual ~ClInfo(); - - /* return 0 if cp may be interested in dev/name else -1 - */ - int findDevice(const std::string &dev, const std::string &name) const; - - /* add the given device and property to the props[] list of client if new. - */ - void addDevice(const std::string &dev, const std::string &name, int isblob); - - virtual void log(const std::string &log) const; - - /* put Msg mp on queue of each chained server client, except notme. - */ - static void q2Servers(DvrInfo *me, Msg *mp, XMLEle *root); - - /* put Msg mp on queue of each client interested in dev/name, except notme. - * if BLOB always honor current mode. - */ - static void q2Clients(ClInfo *notme, int isblob, const std::string &dev, const std::string &name, Msg *mp, XMLEle *root); - - /* Reference to all active clients */ - static ConcurrentSet clients; -}; \ No newline at end of file diff --git a/modules/hydrogen_loader/concurrent.cpp b/modules/hydrogen_loader/concurrent.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/hydrogen_loader/concurrent.hpp b/modules/hydrogen_loader/concurrent.hpp deleted file mode 100644 index 004c743d..00000000 --- a/modules/hydrogen_loader/concurrent.hpp +++ /dev/null @@ -1,145 +0,0 @@ -#pragma once - -#include -#include - -template -class ConcurrentSet -{ - unsigned long identifier = 1; - std::map items; - -public: - void insert(M *item) - { - item->id = identifier++; - items[item->id] = item; - item->current = (ConcurrentSet *)this; - } - - void erase(M *item) - { - items.erase(item->id); - item->id = 0; - item->current = nullptr; - } - - std::vector ids() const - { - std::vector result; - for (auto item : items) - { - result.push_back(item.first); - } - return result; - } - - M *operator[](unsigned long id) const - { - auto e = items.find(id); - if (e == items.end()) - { - return nullptr; - } - return e->second; - } - - class iterator - { - friend class ConcurrentSet; - const ConcurrentSet *parent; - std::vector ids; - // Will be -1 when done - long int pos = 0; - - void skip() - { - if (pos == -1) - return; - while (pos < (long int)ids.size() && !(*parent)[ids[pos]]) - { - pos++; - } - if (pos == (long int)ids.size()) - { - pos = -1; - } - } - - public: - iterator(const ConcurrentSet *parent) : parent(parent) {} - - bool operator!=(const iterator &o) - { - return pos != o.pos; - } - - iterator &operator++() - { - if (pos != -1) - { - pos++; - skip(); - } - return *this; - } - - M *operator*() const - { - return (*parent)[ids[pos]]; - } - }; - - iterator begin() const - { - iterator result(this); - for (auto item : items) - { - result.ids.push_back(item.first); - } - result.skip(); - return result; - } - - iterator end() const - { - iterator result(nullptr); - result.pos = -1; - return result; - } -}; - -/* An object that can be put in a ConcurrentSet, and provide a heartbeat - * to detect removal from ConcurrentSet - */ -class Collectable -{ - template - friend class ConcurrentSet; - unsigned long id = 0; - const ConcurrentSet *current; - - /* Keep the id */ - class HeartBeat - { - friend class Collectable; - unsigned long id; - const ConcurrentSet *current; - HeartBeat(unsigned long id, const ConcurrentSet *current) - : id(id), current(current) {} - - public: - bool alive() const - { - return id != 0 && (*current)[id] != nullptr; - } - }; - -protected: - /* heartbeat.alive will return true as long as this item has not changed collection. - * Also detect deletion of the Collectable */ - HeartBeat heartBeat() const - { - return HeartBeat(id, current); - } -}; \ No newline at end of file diff --git a/modules/hydrogen_loader/driver_info.cpp b/modules/hydrogen_loader/driver_info.cpp deleted file mode 100644 index a6899367..00000000 --- a/modules/hydrogen_loader/driver_info.cpp +++ /dev/null @@ -1,305 +0,0 @@ -#include "driver_info.hpp" - -#include -#include - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#include -#endif - -#include "io.hpp" -#include "lilxml.hpp" -#include "client_info.hpp" -#include "xml_util.hpp" -#include "hydrogen_server.hpp" - -#include "atom/log/loguru.hpp" - -void DvrInfo::onMessage(XMLEle *root, std::list &sharedBuffers) -{ - char *roottag = tagXMLEle(root); - const char *dev = findXMLAttValu(root, "device"); - const char *name = findXMLAttValu(root, "name"); - int isblob = !strcmp(tagXMLEle(root), "setBLOBVector"); - - if (verbose > 2) - traceMsg("read ", root); - else if (verbose > 1) - { - LOG_F(ERROR, "read <{} device='{}' name='{}'>\n", tagXMLEle(root), findXMLAttValu(root, "device"), findXMLAttValu(root, "name")); - } - - /* that's all if driver is just registering a snoop */ - /* JM 2016-05-18: Send getProperties to upstream chained servers as well.*/ - if (!strcmp(roottag, "getProperties")) - { - this->addSDevice(dev, name); - Msg *mp = new Msg(this, root); - /* send to interested chained servers upstream */ - // FIXME: no use of root here - ClInfo::q2Servers(this, mp, root); - /* Send to snooped drivers if they exist so that they can echo back the snooped propertly immediately */ - // FIXME: no use of root here - q2RDrivers(dev, mp, root); - - mp->queuingDone(); - - return; - } - - /* that's all if driver desires to snoop BLOBs from other drivers */ - if (!strcmp(roottag, "enableBLOB")) - { - Property *sp = findSDevice(dev, name); - if (sp) - crackBLOB(pcdataXMLEle(root), &sp->blob); - delXMLEle(root); - return; - } - - /* Found a new device? Let's add it to driver info */ - if (dev[0] && !this->isHandlingDevice(dev)) - { -#ifdef OSX_EMBEDED_MODE - if (this->dev.empty()) - fprintf(stderr, "STARTED \"%s\"\n", dp->name.c_str()); - fflush(stderr); -#endif - this->dev.insert(dev); - } - - /* log messages if any and wanted */ - if (ldir) - logDMsg(root, dev); - - if (!strcmp(roottag, "pingRequest")) - { - setXMLEleTag(root, "pingReply"); - - Msg *mp = new Msg(this, root); - pushMsg(mp); - mp->queuingDone(); - return; - } - - /* build a new message -- set content iff anyone cares */ - Msg *mp = Msg::fromXml(this, root, sharedBuffers); - if (!mp) - { - close(); - return; - } - - /* send to interested clients */ - ClInfo::q2Clients(NULL, isblob, dev, name, mp, root); - - /* send to snooping drivers */ - DvrInfo::q2SDrivers(this, isblob, dev, name, mp, root); - - /* set message content if anyone cares else forget it */ - mp->queuingDone(); -} - -void DvrInfo::closeWritePart() -{ - // Don't want any half-dead drivers - close(); -} - -void DvrInfo::close() -{ - // Tell client driver is dead. - for (auto dev : dev) - { - /* Inform clients that this driver is dead */ - XMLEle *root = addXMLEle(NULL, "delProperty"); - addXMLAtt(root, "device", dev.c_str()); - - prXMLEle(stderr, root, 0); - Msg *mp = new Msg(this, root); - - ClInfo::q2Clients(NULL, 0, dev.c_str(), "", mp, root); - mp->queuingDone(); - } - - bool terminate; - if (!restart) - { - terminate = true; - } - else - { - if (restarts >= maxrestarts) - { - log(fmt::format("Terminated after #{} restarts.", restarts)); - terminate = true; - } - else - { - log(fmt::format("restart #{}", restarts)); - ++restarts; - terminate = false; - } - } - -#ifdef OSX_EMBEDED_MODE - fprintf(stderr, "STOPPED \"%s\"\n", name.c_str()); - fflush(stderr); -#endif - - // FIXME: we loose stderr from dying driver - if (terminate) - { - delete (this); - if ((!fifo) && (drivers.ids().empty())) - // Bye(); - return; - } - else - { - DvrInfo *restarted = this->clone(); - delete (this); - restarted->start(); - } -} - -void DvrInfo::q2RDrivers(const std::string &dev, Msg *mp, XMLEle *root) -{ - char *roottag = tagXMLEle(root); - - /* queue message to each interested driver. - * N.B. don't send generic getProps to more than one remote driver, - * otherwise they all fan out and we get multiple responses back. - */ - std::set remoteAdvertised; - for (auto dpId : drivers.ids()) - { - auto dp = drivers[dpId]; - if (dp == nullptr) - continue; - - std::string remoteUid = dp->remoteServerUid(); - bool isRemote = !remoteUid.empty(); - - /* driver known to not support this dev */ - if ((!dev.empty()) && dev[0] != '*' && !dp->isHandlingDevice(dev)) - continue; - - /* Only send message to each *unique* remote driver at a particular host:port - * Since it will be propogated to all other devices there */ - if (dev.empty() && isRemote) - { - if (remoteAdvertised.find(remoteUid) != remoteAdvertised.end()) - continue; - - /* Retain last remote driver data so that we do not send the same info again to a driver - * residing on the same host:port */ - remoteAdvertised.insert(remoteUid); - } - - /* JM 2016-10-30: Only send enableBLOB to remote drivers */ - if (isRemote == 0 && !strcmp(roottag, "enableBLOB")) - continue; - - // pushmsg can kill dp. do at end - dp->pushMsg(mp); - } -} - -void DvrInfo::q2SDrivers(DvrInfo *me, int isblob, const std::string &dev, const std::string &name, Msg *mp, XMLEle *root) -{ - std::string meRemoteServerUid = me ? me->remoteServerUid() : ""; - for (auto dpId : drivers.ids()) - { - auto dp = drivers[dpId]; - if (dp == nullptr) - continue; - - Property *sp = dp->findSDevice(dev, name); - - /* nothing for dp if not snooping for dev/name or wrong BLOB mode */ - if (!sp) - continue; - if ((isblob && sp->blob == B_NEVER) || (!isblob && sp->blob == B_ONLY)) - continue; - - // Do not send snoop data to remote drivers at the same host - // since they will manage their own snoops remotely - if ((!meRemoteServerUid.empty()) && dp->remoteServerUid() == meRemoteServerUid) - continue; - - // pushmsg can kill dp. do at end - dp->pushMsg(mp); - } -} - -void DvrInfo::addSDevice(const std::string &dev, const std::string &name) -{ - Property *sp; - - /* no dups */ - sp = findSDevice(dev, name); - if (sp) - return; - - /* add dev to sdevs list */ - sp = new Property(dev, name); - sp->blob = B_NEVER; - sprops.push_back(sp); - - if (verbose) - DLOG_F(INFO, "snooping on %s.%s\n", dev.c_str(), name.c_str()); -} - -Property *DvrInfo::findSDevice(const std::string &dev, const std::string &name) const -{ - for (auto sp : sprops) - { - if ((sp->dev == dev) && (sp->name.empty() || sp->name == name)) - return (sp); - } - - return nullptr; -} - -DvrInfo::DvrInfo(bool useSharedBuffer) : MsgQueue(useSharedBuffer), - restarts(0) -{ - drivers.insert(this); -} - -DvrInfo::DvrInfo(const DvrInfo &model) : MsgQueue(model.useSharedBuffer), - name(model.name), - restarts(model.restarts) -{ - drivers.insert(this); -} - -DvrInfo::~DvrInfo() -{ - drivers.erase(this); - for (auto prop : sprops) - { - delete prop; - } -} - -bool DvrInfo::isHandlingDevice(const std::string &dev) const -{ - return this->dev.find(dev) != this->dev.end(); -} - -void DvrInfo::log(const std::string &str) const -{ - std::string logLine = "Driver "; - logLine += name; - logLine += ": "; - logLine += str; - DLOG_F(INFO, "{}", logLine.c_str()); -} diff --git a/modules/hydrogen_loader/driver_info.hpp b/modules/hydrogen_loader/driver_info.hpp deleted file mode 100644 index 4cc3ebc3..00000000 --- a/modules/hydrogen_loader/driver_info.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "message_queue.hpp" -#include "property.hpp" -#include "concurrent.hpp" - -/* info for each connected driver */ -class DvrInfo : public MsgQueue -{ - /* add dev/name to this device's snooping list. - * init with blob mode set to B_NEVER. - */ - void addSDevice(const std::string &dev, const std::string &name); - -public: - /* return Property if dp is this driver is snooping dev/name, else NULL. - */ - Property *findSDevice(const std::string &dev, const std::string &name) const; - -protected: - /* send message to each interested client - */ - virtual void onMessage(XMLEle *root, std::list &sharedBuffers); - - /* override to kill driver that are not reachable anymore */ - virtual void closeWritePart(); - - /* Construct an instance that will start the same driver */ - DvrInfo(const DvrInfo &model); - -public: - std::string name; /* persistent name */ - - std::set dev; /* device served by this driver */ - std::list sprops; /* props we snoop */ - int restarts; /* times process has been restarted */ - bool restart = true; /* Restart on shutdown */ - - DvrInfo(bool useSharedBuffer); - virtual ~DvrInfo(); - - bool isHandlingDevice(const std::string &dev) const; - - /* start the HYDROGEN driver process or connection. - * exit if trouble. - */ - virtual void start() = 0; - - /* close down the given driver and restart if set*/ - virtual void close(); - - /* Allocate an instance that will start the same driver */ - virtual DvrInfo *clone() const = 0; - - virtual void log(const std::string &log) const; - - virtual const std::string remoteServerUid() const = 0; - - /* put Msg mp on queue of each driver responsible for dev, or all drivers - * if dev empty. - */ - static void q2RDrivers(const std::string &dev, Msg *mp, XMLEle *root); - - /* put Msg mp on queue of each driver snooping dev/name. - * if BLOB always honor current mode. - */ - static void q2SDrivers(DvrInfo *me, int isblob, const std::string &dev, const std::string &name, Msg *mp, XMLEle *root); - - /* Reference to all active drivers */ - static ConcurrentSet drivers; - - // decoding of attached blobs from driver is not supported ATM. Be conservative here - virtual bool acceptSharedBuffers() const - { - return false; - } -}; diff --git a/modules/hydrogen_loader/fifo_server.cpp b/modules/hydrogen_loader/fifo_server.cpp deleted file mode 100644 index 6d4bcc09..00000000 --- a/modules/hydrogen_loader/fifo_server.cpp +++ /dev/null @@ -1,451 +0,0 @@ -#include "fifo_server.hpp" - -#include -#include -#include -#include - -#include "io.hpp" -#include "driver_info.hpp" -#include "local_driver.hpp" -#include "remote_driver.hpp" -#include "hydrogen_server.hpp" - -#ifdef USE_LIBUV - -Fifo::Fifo(const std::string &name) : name(name) -{ - pollHandle.data = this; -} - -/* Attempt to open up FIFO */ -void Fifo::close() -{ - if (fd != -1) - { - ::close(fd); - fd = -1; - uv_poll_stop(&pollHandle); - } - bufferPos = 0; -} - -void Fifo::open() -{ - /* Open up FIFO, if available */ -#ifdef _WIN32 - fd = ::open(name.c_str(), O_RDONLY); -#else - fd = ::open(name.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC); -#endif - - if (fd < 0) - { - // log(fmt("open(%s): %s.\n", name.c_str(), strerror(errno))); - // Bye(); - } - - uv_poll_init(uv_default_loop(), &pollHandle, fd); - uv_poll_start(&pollHandle, UV_READABLE, ioCb); -} - -/* Handle one fifo command. Start/stop drivers accordingly */ -void Fifo::processLine(const char *line) -{ - - // log(fmt("FIFO: %s\n", line)); - - char cmd[MAXSBUF], arg[4][1], var[4][MAXSBUF], tDriver[MAXSBUF], tName[MAXSBUF], envConfig[MAXSBUF], - envSkel[MAXSBUF], envPrefix[MAXSBUF]; - - memset(&tDriver[0], 0, sizeof(char) * MAXSBUF); - memset(&tName[0], 0, sizeof(char) * MAXSBUF); - memset(&envConfig[0], 0, sizeof(char) * MAXSBUF); - memset(&envSkel[0], 0, sizeof(char) * MAXSBUF); - memset(&envPrefix[0], 0, sizeof(char) * MAXSBUF); - - int n = 0; - - bool remoteDriver = !!strstr(line, "@"); - - // If remote driver - if (remoteDriver) - { - n = sscanf(line, "%s %511[^\n]", cmd, tDriver); - - // Remove quotes if any - char *ptr = tDriver; - int len = strlen(tDriver); - while ((ptr = strstr(tDriver, "\""))) - { - memmove(ptr, ptr + 1, --len); - ptr[len] = '\0'; - } - } - // If local driver - else - { - n = sscanf(line, "%s %s -%1c \"%511[^\"]\" -%1c \"%511[^\"]\" -%1c \"%511[^\"]\" -%1c \"%511[^\"]\"", cmd, - tDriver, arg[0], var[0], arg[1], var[1], arg[2], var[2], arg[3], var[3]); - } - - int n_args = (n - 2) / 2; - - int j = 0; - for (j = 0; j < n_args; j++) - { - if (arg[j][0] == 'n') - { - strncpy(tName, var[j], MAXSBUF - 1); - tName[MAXSBUF - 1] = '\0'; - } - else if (arg[j][0] == 'c') - { - strncpy(envConfig, var[j], MAXSBUF - 1); - envConfig[MAXSBUF - 1] = '\0'; - } - else if (arg[j][0] == 's') - { - strncpy(envSkel, var[j], MAXSBUF - 1); - envSkel[MAXSBUF - 1] = '\0'; - } - else if (arg[j][0] == 'p') - { - strncpy(envPrefix, var[j], MAXSBUF - 1); - envPrefix[MAXSBUF - 1] = '\0'; - } - } - - bool startCmd; - if (!strcmp(cmd, "start")) - startCmd = 1; - else - startCmd = 0; - - if (startCmd) - { - - DvrInfo *dp; - if (remoteDriver == 0) - { - auto *localDp = new LocalDvrInfo(); - dp = localDp; - // strncpy(dp->dev, tName, MAXSBUF); - localDp->envDev = tName; - localDp->envConfig = envConfig; - localDp->envSkel = envSkel; - localDp->envPrefix = envPrefix; - } - else - { - dp = new RemoteDvrInfo(); - } - dp->name = tDriver; - dp->start(); - } - else - { - for (auto dp : DvrInfo::drivers) - { - if (dp == nullptr) - continue; - - if (dp->name == tDriver) - { - /* If device name is given, check against it before shutting down */ - if (tName[0] && !dp->isHandlingDevice(tName)) - continue; - if (verbose) - // log(fmt("FIFO: Shutting down driver: %s\n", tDriver)); - - dp->restart = false; - dp->close(); - break; - } - } - } -} - -void Fifo::read() -{ - int rd = ::read(fd, buffer + bufferPos, sizeof(buffer) - 1 - bufferPos); - if (rd == 0) - { - if (bufferPos > 0) - { - buffer[bufferPos] = '\0'; - processLine(buffer); - } - close(); - open(); - return; - } - if (rd == -1) - { - if (errno == EAGAIN || errno == EWOULDBLOCK) - return; - - // log(fmt("Fifo error: %s\n", strerror(errno))); - close(); - open(); - return; - } - - bufferPos += rd; - - for (int i = 0; i < bufferPos; ++i) - { - if (buffer[i] == '\n') - { - buffer[i] = 0; - processLine(buffer); - // shift the buffer - i++; /* count including nl */ - bufferPos -= i; /* remove from nexbuf */ - memmove(buffer, buffer + i, bufferPos); /* slide remaining to front */ - i = -1; /* restart for loop scan */ - } - } - - if ((unsigned)bufferPos >= sizeof(buffer) - 1) - { - // log(fmt("Fifo overflow")); - close(); - open(); - } -} - -void Fifo::ioCb(uv_poll_t *handle, int status, int revents) -{ - Fifo *fifo = static_cast(handle->data); - if (status < 0) - { - int sockErrno = readFdError(fifo->fd); - if (sockErrno) - { - // log(fmt("Error on fifo: %s\n", strerror(sockErrno))); - fifo->close(); - fifo->open(); - } - } - else if (revents & UV_READABLE) - { - fifo->read(); - } -} - -#else - -Fifo::Fifo(const std::string &name) : name(name) -{ - fdev.set(this); -} - -/* Attempt to open up FIFO */ -void Fifo::close(void) -{ - if (fd != -1) - { - ::close(fd); - fd = -1; - fdev.stop(); - } - bufferPos = 0; -} - -void Fifo::open() -{ - /* Open up FIFO, if available */ -#ifdef _WIN32 - fd = ::open(name.c_str(), O_RDONLY); -#else - fd = ::open(name.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC); -#endif - - if (fd < 0) - { - // log(fmt("open(%s): %s.\n", name.c_str(), strerror(errno))); - // Bye(); - } - - fdev.start(fd, EV_READ); -} - -/* Handle one fifo command. Start/stop drivers accordingly */ -void Fifo::processLine(const char *line) -{ - char cmd[MAXSBUF], arg[4][2], var[4][MAXSBUF], tDriver[MAXSBUF], tName[MAXSBUF], - envConfig[MAXSBUF], envSkel[MAXSBUF], envPrefix[MAXSBUF]; - - std::memset(&tDriver[0], 0, sizeof(char) * MAXSBUF); - std::memset(&tName[0], 0, sizeof(char) * MAXSBUF); - std::memset(&envConfig[0], 0, sizeof(char) * MAXSBUF); - std::memset(&envSkel[0], 0, sizeof(char) * MAXSBUF); - std::memset(&envPrefix[0], 0, sizeof(char) * MAXSBUF); - - int n = 0; - - bool remoteDriver = !!std::strstr(line, "@"); - - // If remote driver - if (remoteDriver) - { - n = std::sscanf(line, "%s %511[^\n]", cmd, tDriver); - - // Remove quotes if any - char *ptr = tDriver; - int len = std::strlen(tDriver); - while ((ptr = std::strstr(tDriver, "\""))) - { - std::memmove(ptr, ptr + 1, --len); - ptr[len] = '\0'; - } - } - // If local driver - else - { - n = std::sscanf(line, "%s %s -%1c \"%511[^\"]\" -%1c \"%511[^\"]\" -%1c \"%511[^\"]\" -%1c \"%511[^\"]\"", - cmd, tDriver, arg[0], var[0], arg[1], var[1], arg[2], var[2], arg[3], var[3]); - } - - int n_args = (n - 2) / 2; - - int j = 0; - for (j = 0; j < n_args; j++) - { - if (arg[j][0] == 'n') - { - strncpy(tName, var[j], MAXSBUF - 1); - tName[MAXSBUF - 1] = '\0'; - } - else if (arg[j][0] == 'c') - { - strncpy(envConfig, var[j], MAXSBUF - 1); - envConfig[MAXSBUF - 1] = '\0'; - } - else if (arg[j][0] == 's') - { - strncpy(envSkel, var[j], MAXSBUF - 1); - envSkel[MAXSBUF - 1] = '\0'; - } - else if (arg[j][0] == 'p') - { - strncpy(envPrefix, var[j], MAXSBUF - 1); - envPrefix[MAXSBUF - 1] = '\0'; - } - } - - bool startCmd; - if (!std::strcmp(cmd, "start")) - startCmd = true; - else - startCmd = false; - - if (startCmd) - { - DvrInfo *dp; - if (remoteDriver == false) - { - auto *localDp = new LocalDvrInfo(); - dp = localDp; - localDp->envDev = tName; - localDp->envConfig = envConfig; - localDp->envSkel = envSkel; - localDp->envPrefix = envPrefix; - } - else - { - dp = new RemoteDvrInfo(); - } - dp->name = tDriver; - dp->start(); - } - else - { - for (auto dp : DvrInfo::drivers) - { - if (dp == nullptr) - continue; - - if (dp->name == tDriver) - { - // If device name is given, check against it before shutting down - if (tName[0] && !dp->isHandlingDevice(tName)) - continue; - - dp->restart = false; - dp->close(); - break; - } - } - } -} - -void Fifo::read(void) -{ - int rd = ::read(fd, buffer + bufferPos, sizeof(buffer) - 1 - bufferPos); - if (rd == 0) - { - if (bufferPos > 0) - { - buffer[bufferPos] = '\0'; - processLine(buffer); - } - close(); - open(); - return; - } - if (rd == -1) - { - if (errno == EAGAIN || errno == EWOULDBLOCK) - return; - - // log(fmt("Fifo error: %s\n", strerror(errno))); - close(); - open(); - return; - } - - bufferPos += rd; - - for (int i = 0; i < bufferPos; ++i) - { - if (buffer[i] == '\n') - { - buffer[i] = 0; - processLine(buffer); - // shift the buffer - i++; /* count including nl */ - bufferPos -= i; /* remove from nexbuf */ - memmove(buffer, buffer + i, bufferPos); /* slide remaining to front */ - i = -1; /* restart for loop scan */ - } - } - - if ((unsigned)bufferPos >= sizeof(buffer) - 1) - { - // log(fmt("Fifo overflow")); - close(); - open(); - } -} - -void Fifo::ioCb(ev::io &, int revents) -{ - if (EV_ERROR & revents) - { - int sockErrno = readFdError(this->fd); - if (sockErrno) - { - // log(fmt("Error on fifo: %s\n", strerror(sockErrno))); - close(); - open(); - } - } - else if (revents & EV_READ) - { - read(); - } -} - -#endif diff --git a/modules/hydrogen_loader/fifo_server.hpp b/modules/hydrogen_loader/fifo_server.hpp deleted file mode 100644 index 0e1290d4..00000000 --- a/modules/hydrogen_loader/fifo_server.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include - -#ifdef USE_LIBUV - -#include -#include - -class Fifo -{ - std::string name; /* Path to FIFO for dynamic startups & shutdowns of drivers */ - - char buffer[1024]; - int bufferPos = 0; - int fd = -1; - uv_poll_t pollHandle; - - void close(); - void open(); - void processLine(const char *line); - - /* Read commands from FIFO and process them. Start/stop drivers accordingly */ - void read(); - static void ioCb(uv_poll_t* handle, int status, int revents); - -public: - Fifo(const std::string& name); - void listen() - { - open(); - } -}; - -#else - -#include - -class Fifo -{ -public: - Fifo(const std::string &name); - - void listen() - { - open(); - } - - void processLine(const char *line); - -private: - void close(); - void open(); - /* Read commands from FIFO and process them. Start/stop drivers accordingly */ - void read(); - void ioCb(ev::io &watcher, int revents); - - std::string name; /* Path to FIFO for dynamic startups & shutdowns of drivers */ - char buffer[1024]; - int bufferPos = 0; - int fd = -1; - ev::io fdev; -}; -#endif \ No newline at end of file diff --git a/modules/hydrogen_loader/hydrogen_server.cpp b/modules/hydrogen_loader/hydrogen_server.cpp deleted file mode 100644 index a8526682..00000000 --- a/modules/hydrogen_loader/hydrogen_server.cpp +++ /dev/null @@ -1,422 +0,0 @@ -#include "config.h" -#include -#include -#include -#include -#if ENABLE_FASTHASH -#include "emhash/hash_table8.hpp" -#else -#include -#endif -#include -#include -#include - -#include - -#include "hydrogenapi.h" -#include "hydrogendevapi.hpp" -#include "sharedblob.hpp" -#include "lilxml.hpp" -#include "base64.hpp" - -#include "client_info.hpp" -#include "concurrent.hpp" -#include "driver_info.hpp" -#include "local_driver.hpp" -#include "remote_driver.hpp" - -#include "io.hpp" -#include "message_queue.hpp" -#include "message.hpp" -#include "property.hpp" -#include "serialize.hpp" -#include "tcp_server.hpp" -#include "time.hpp" -#include "xml_util.hpp" -#include "signal.hpp" - -#include "hydrogen_server.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef MSG_ERRQUEUE -#include -#endif -#endif - -#ifdef USE_LIBUV -#include -#else -#include -#endif - -#include "atom/log/loguru.hpp" -#include "backward/backward.hpp" - -extern ConcurrentSet ClInfo::clients; -extern ConcurrentSet DvrInfo::drivers; - -/* print usage message and exit (2) */ -static void usage(void) -{ - fprintf(stderr, "Usage: %s [options] driver [driver ...]\n", me); - fprintf(stderr, "Purpose: server for local and remote HYDROGEN drivers\n"); - fprintf(stderr, "HYDROGEN Protocol %g.\n", HYDROGENV); - fprintf(stderr, "Options:\n"); - fprintf(stderr, " -l d : log driver messages to /YYYY-MM-DD.islog\n"); - fprintf(stderr, " -m m : kill client if gets more than this many MB behind, default %d\n", DEFMAXQSIZ); - fprintf(stderr, - " -d m : drop streaming blobs if client gets more than this many MB behind, default %d. 0 to disable\n", - DEFMAXSSIZ); -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - fprintf(stderr, " -u path : Path for the local connection socket (abstract), default %s\n", HYDROGENUNIXSOCK); -#endif - fprintf(stderr, " -p p : alternate IP port, default %d\n", HYDROGENPORT); - fprintf(stderr, " -r r : maximum driver restarts on error, default %d\n", DEFMAXRESTART); - fprintf(stderr, " -f path : Path to fifo for dynamic startup and shutdown of drivers.\n"); - fprintf(stderr, " -v : show key events, no traffic\n"); - fprintf(stderr, " -vv : -v + key message content\n"); - fprintf(stderr, " -vvv : -vv + complete xml\n"); - fprintf(stderr, "driver : executable or [device]@host[:port]\n"); - - exit(2); -} - -/* -#ifdef _WIN32 -static void noSIGPIPE() -{ - SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); -} -#else -static void noSIGPIPE() -{ - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = SIG_IGN; - sigemptyset(&sa.sa_mask); - (void)sigaction(SIGPIPE, &sa, NULL); -} -#endif -*/ -void noSIGPIPE() -{ - SignalHandler::registerHandler(SIGPIPE, []() {}); -} - -void cleanup() -{ - SignalHandler::unregisterHandler(SIGPIPE); -} - -#ifdef MAIN_FUNC - -int main(int ac, char *av[]) -{ - /* save our name */ - me = av[0]; - -#ifdef OSX_EMBEDED_MODE - - char logname[128]; - snprintf(logname, 128, LOGNAME, getlogin()); - fprintf(stderr, "switching stderr to %s", logname); - freopen(logname, "w", stderr); - - fifo = new Fifo(); - fifo->name = FIFONAME; - verbose = 1; - ac = 0; - -#else - - /* crack args */ - /* - Old: - while ((--ac > 0) && ((*++av)[0] == '-')) - { - char *s; - for (s = av[0] + 1; *s != '\0'; s++) - switch (*s) - { - case 'l': - if (ac < 2) - { - fprintf(stderr, "-l requires log directory\n"); - usage(); - } - ldir = *++av; - ac--; - break; - case 'm': - if (ac < 2) - { - fprintf(stderr, "-m requires max MB behind\n"); - usage(); - } - maxqsiz = 1024 * 1024 * atoi(*++av); - ac--; - break; - case 'p': - if (ac < 2) - { - fprintf(stderr, "-p requires port value\n"); - usage(); - } - port = atoi(*++av); - ac--; - break; - case 'd': - if (ac < 2) - { - fprintf(stderr, "-d requires max stream MB behind\n"); - usage(); - } - maxstreamsiz = 1024 * 1024 * atoi(*++av); - ac--; - break; -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - case 'u': - if (ac < 2) - { - fprintf(stderr, "-u requires local socket path\n"); - usage(); - } - UnixServer::unixSocketPath = *++av; - ac--; - break; -#endif // ENABLE_HYDROGEN_SHARED_MEMORY - case 'f': - if (ac < 2) - { - fprintf(stderr, "-f requires fifo node\n"); - usage(); - } - fifo = new Fifo(*++av); - ac--; - break; - case 'r': - if (ac < 2) - { - fprintf(stderr, "-r requires number of restarts\n"); - usage(); - } - maxrestarts = atoi(*++av); - if (maxrestarts < 0) - maxrestarts = 0; - ac--; - break; - case 'v': - verbose++; - break; - default: - usage(); - } - } - */ - int opt; - while ((opt = getopt(ac, av, "l:m:p:d:u:f:r:v")) != -1) - { - switch (opt) - { - case 'l': - ldir = optarg; - break; - case 'm': - maxqsiz = std::stoi(optarg) * 1024 * 1024; - break; - case 'p': - port = std::stoi(optarg); - break; - case 'd': - maxstreamsiz = std::stoi(optarg) * 1024 * 1024; - break; -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - case 'u': - UnixServer::unixSocketPath = optarg; - break; -#endif // ENABLE_HYDROGEN_SHARED_MEMORY - case 'f': - fifo = new Fifo(optarg); - break; - case 'r': - maxrestarts = std::stoi(optarg); - if (maxrestarts < 0) - maxrestarts = 0; - break; - case 'v': - verbose++; - break; - default: // '?' - usage(); - } - } - -#endif - - /* at this point there are ac args in av[] to name our drivers */ - if (ac == 0 && !fifo) - usage(); - - /* take care of some unixisms */ - noSIGPIPE(); - - /* start each driver */ - /* Old: - while (ac-- > 0) - { - std::string dvrName = *av++; - DvrInfo *dr; - if (dvrName.find('@') != std::string::npos) - { - dr = new RemoteDvrInfo(); - } - else - { - dr = new LocalDvrInfo(); - } - dr->name = dvrName; - dr->start(); - } - */ - DLOG_F(INFO, "Start loading driver..."); - int count = ac - 1; - std::vector> drivers; - for (int i = 0; i < count; i++) - { - std::string dvrName = av[i + 1]; - std::shared_ptr driver; - if (dvrName.find('@') != std::string::npos) - { - driver = std::make_unique(); - } - else - { - driver = std::make_unique(); - } - driver->name = dvrName; - drivers.push_back(driver); - drivers[i]->start(); - DLOG_F(INFO, "Started {}", driver->name); - } - - /* announce we are online */ - // Old: (new TcpServer(port))->listen(); - std::shared_ptr tcp_server; - tcp_server = std::make_shared(port); - tcp_server->listen(); - -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - /* create a new unix server */ - (new UnixServer(UnixServer::unixSocketPath))->listen(); -#endif - /* Load up FIFO, if available */ - if (fifo) - { - // New started drivers will not inherit server's prefix anymore - - // JM 2022.07.23: This causes an issue on MacOS. Disabled for now until investigated further. - // unsetenv("HYDROGENPREFIX"); - DLOG_F(INFO, "Starting FIFO server"); - fifo->listen(); - } - - /* handle new clients and all io */ - DLOG_F(INFO, "Main loop started"); -#ifdef USE_LIBUV - uv_run(loop, UV_RUN_DEFAULT); -#else - loop.loop(); -#endif - - /* will not happen unless no more listener left ! */ - LOG_F(ERROR, "unexpected return from event loop"); - return (1); -} - -#else - -void run_hydrogen_server(std::unordered_map m_params) -{ - /* save our name */ - me = "hydrogen_server_inside"; - - std::shared_ptr tcp_server; - tcp_server = std::make_shared(port); - tcp_server->listen(); - - fifo = new Fifo(); - fifo->name = "/tmp/hydrogenserverFIFO"; - /* Load up FIFO, if available */ - if (fifo) - { - DLOG_F(INFO, "Starting FIFO server"); - fifo->listen(); - } - - /* handle new clients and all io */ - DLOG_F(INFO, "Main loop started"); -#ifdef USE_LIBUV - uv_run(loop, UV_RUN_DEFAULT); -#else - loop.loop(); -#endif - /* will not happen unless no more listener left ! */ - LOG_F(ERROR, "unexpected return from event loop"); -} - -void start_hydrogen_driver(const std::string &driver_binary,const std::string &driver_skeleton) -{ - std::string cmd = "start " + driver->binary; - if (!driver_skeleton.empty()) - { - cmd += " -s \"" + driver->skeleton + "\""; - } - cmd = std::regex_replace(cmd, std::regex("\""), "\\\""); - if (fifo) - { - fifo->processLine(cmd.c_str()); - } -} - -void stop_hydrogen_driver(const std::string &driver_binary, const std::string &driver_lable = "") -{ - std::string cmd = "stop " + driver_binary; - if (driver_binary.find("@") == std::string::npos) - { - cmd += " -n \"" + driver_label + "\""; - } - cmd = std::regex_replace(cmd, std::regex("\""), "\\\""); - if (fifo) - { - fifo->processLine(cmd.c_str()); - } -} -#endif diff --git a/modules/hydrogen_loader/hydrogen_server.hpp b/modules/hydrogen_loader/hydrogen_server.hpp deleted file mode 100644 index 406ee199..00000000 --- a/modules/hydrogen_loader/hydrogen_server.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#ifndef MAIN_FUNC -#if ENABLE_FASTHASH -#include "emhash/hash_table8.hpp" -#else -#include -#endif -#endif - -#include "fifo_server.hpp" -#include "driver_info.hpp" -#include "client_info.hpp" -#include "concurrent.hpp" - -#define HYDROGENPORT 7624 /* default TCP/IP port to listen */ -#define HYDROGENUNIXSOCK "/tmp/hydrogenserver" /* default unix socket path (local connections) */ -#define MAXSBUF 512 -#define MAXRBUF 49152 /* max read buffering here */ -#define MAXWSIZ 49152 /* max bytes/write */ -#define SHORTMSGSIZ 2048 /* buf size for most messages */ -#define DEFMAXQSIZ 128 /* default max q behind, MB */ -#define DEFMAXSSIZ 5 /* default max stream behind, MB */ -#define DEFMAXRESTART 10 /* default max restarts */ -#define MAXFD_PER_MESSAGE 16 /* No more than 16 buffer attached to a message */ -#ifdef OSX_EMBEDED_MODE -#define LOGNAME "/Users/%s/Library/Logs/hydrogenserver.log" -#define FIFONAME "/tmp/hydrogenserverFIFO" -#endif - -#define STRINGIFY_TOK(x) #x -#define TO_STRING(x) STRINGIFY_TOK(x) - -#ifdef USE_LIBUV -static uv_loop_t* loop = uv_default_loop(); -#else -static ev::default_loop loop; -#endif -static Fifo *fifo = nullptr; -static const char *me; /* our name */ -static int port = HYDROGENPORT; /* public HYDROGEN port */ -static int verbose; /* chattiness */ -static char *ldir; /* where to log driver messages */ -static unsigned int maxqsiz = (DEFMAXQSIZ * 1024 * 1024); /* kill if these bytes behind */ -static unsigned int maxstreamsiz = (DEFMAXSSIZ * 1024 * 1024); /* drop blobs if these bytes behind while streaming*/ -static int maxrestarts = DEFMAXRESTART; - -#ifndef MAIN_FUNC -void run_hydrogen_server(std::unordered_map m_params); -void start_hydrogen_driver(const std::string &driver_binary,const std::string &driver_skeleton); -void stop_hydrogen_driver(const std::string &driver_binary, const std::string &driver_lable); -#endif \ No newline at end of file diff --git a/modules/hydrogen_loader/io.cpp b/modules/hydrogen_loader/io.cpp deleted file mode 100644 index 43878baa..00000000 --- a/modules/hydrogen_loader/io.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "io.hpp" - -#ifdef _WIN32 -#include -#else -#include -#include -#include -#include -#include -#include -#endif - -#include - -int readFdError(int fd) -{ -#ifdef MSG_ERRQUEUE - char rcvbuf[128]; /* Buffer for normal data (not expected here...) */ - char cbuf[512]; /* Buffer for ancillary data (errors) */ - struct iovec iov; - struct msghdr msg; - - iov.iov_base = &rcvbuf; - iov.iov_len = sizeof(rcvbuf); - - msg.msg_name = nullptr; - msg.msg_namelen = 0; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_flags = 0; - msg.msg_control = cbuf; - msg.msg_controllen = sizeof(cbuf); - - int recv_bytes = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); - if (recv_bytes == -1) - { - if (errno == EAGAIN || errno == EWOULDBLOCK) - return 0; - return errno; - } - - /* Receive auxiliary data in msgh */ - for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) - { - fprintf(stderr, "cmsg_len=%zu, cmsg_level=%u, cmsg_type=%u\n", cmsg->cmsg_len, cmsg->cmsg_level, cmsg->cmsg_type); - - if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR) - { - return ((struct sock_extended_err *)CMSG_DATA(cmsg))->ee_errno; - } - } -#else - (void)fd; -#endif - - // Default to EIO as a generic error path - return EIO; -} - -#ifdef _WIN32 -void* attachSharedBuffer(HANDLE fileHandle, size_t& size) -{ - SIZE_T fileSize; - if (!GetFileSizeEx(fileHandle, reinterpret_cast(&fileSize))) - { - perror("invalid shared buffer file handle"); - //Bye(); - } - size = fileSize; - - HANDLE mappingHandle = CreateFileMapping(fileHandle, nullptr, PAGE_READONLY, 0, 0, nullptr); - if (mappingHandle == nullptr) - { - perror("CreateFileMapping"); - //Bye(); - } - - void* ret = MapViewOfFile(mappingHandle, FILE_MAP_READ, 0, 0, 0); - CloseHandle(mappingHandle); - - if (ret == nullptr) - { - perror("MapViewOfFile"); - //Bye(); - } - - return ret; -} - -void detachSharedBuffer(void* ptr) -{ - if (!UnmapViewOfFile(ptr)) - { - perror("shared buffer UnmapViewOfFile"); - //Bye(); - } -} - -#else -void *attachSharedBuffer(int fd, size_t &size) -{ - struct stat sb; - if (fstat(fd, &sb) == -1) - { - perror("invalid shared buffer fd"); - //Bye(); - } - size = sb.st_size; - void *ret = mmap(0, size, PROT_READ, MAP_SHARED, fd, 0); - - if (ret == MAP_FAILED) - { - perror("mmap"); - //Bye(); - } - - return ret; -} - -void detachSharedBuffer(int fd, void *ptr, size_t size) -{ - (void)fd; - if (munmap(ptr, size) == -1) - { - perror("shared buffer munmap"); - //Bye(); - } -} -#endif \ No newline at end of file diff --git a/modules/hydrogen_loader/io.hpp b/modules/hydrogen_loader/io.hpp deleted file mode 100644 index 48187fbc..00000000 --- a/modules/hydrogen_loader/io.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -#ifdef _WIN32 -#include -#endif - -int readFdError(int fd); -#ifdef _WIN32 -void* attachSharedBuffer(HANDLE fileHandle, size_t& size); -void detachSharedBuffer(void* ptr); -#else -void *attachSharedBuffer(int fd, size_t &size); -void detachSharedBuffer(int fd, void *ptr, size_t size); -#endif \ No newline at end of file diff --git a/modules/hydrogen_loader/local_driver.cpp b/modules/hydrogen_loader/local_driver.cpp deleted file mode 100644 index 65bb6b1d..00000000 --- a/modules/hydrogen_loader/local_driver.cpp +++ /dev/null @@ -1,535 +0,0 @@ -#include "local_driver.hpp" - -#include -#include - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#include -#endif - -#include "io.hpp" -#include "lilxml.hpp" -#include "client_info.hpp" -#include "xml_util.hpp" -#include "hydrogen_server.hpp" - -#include "atom/log/loguru.hpp" - -LocalDvrInfo::LocalDvrInfo() : DvrInfo(true) -{ -#ifdef USE_LIBUV - eio.data = this; - pidwatcher.data = this; -#else - eio.set(this); - pidwatcher.set(this); -#endif -} - -LocalDvrInfo::LocalDvrInfo(const LocalDvrInfo &model) : DvrInfo(model), - envDev(model.envDev), - envConfig(model.envConfig), - envSkel(model.envSkel), - envPrefix(model.envPrefix) -{ -#ifdef USE_LIBUV - eio.data = this; - pidwatcher.data = this; -#else - eio.set(this); - pidwatcher.set(this); -#endif -} - -LocalDvrInfo::~LocalDvrInfo() -{ - closeEfd(); - if (pid != 0) - { - kill(pid, SIGKILL); /* libev insures there will be no zombies */ - pid = 0; - } - closePid(); -} - -/* start the given local HYDROGEN driver process. - * exit if trouble. - */ -#ifdef _WIN32 -void LocalDvrInfo::start() -{ - Msg *mp; - HANDLE hReadPipe, hWritePipe, hErrorPipe; - SECURITY_ATTRIBUTES sa; - PROCESS_INFORMATION pi; - STARTUPINFO si; - HANDLE hEfd; - DWORD pid; - -#ifdef OSX_EMBEDED_MODE - fprintf(stderr, "STARTING \"%s\"\n", name.c_str()); - fflush(stderr); -#endif - - HANDLE hRp, hWp, hEp; // 修改变量声明为HANDLE类型 - - /* build three pipes: r, w and error*/ - if (useSharedBuffer) - { - // FIXME: lots of FD are opened by hydrogenserver. FD_CLOEXEC is a must + check other fds - sa.nLength = sizeof(SECURITY_ATTRIBUTES); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = NULL; - if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) - { - log(fmt::format("CreatePipe: %d\n", GetLastError())); - // Bye(); - } - hErrorPipe = hWritePipe; - } - else - { - - if (!CreatePipe(&hRp, &hWp, NULL, 4096)) - { - log(fmt::format("CreatePipe read: %d\n", GetLastError())); - // Bye(); - } - if (!CreatePipe(&hRp, &hEp, NULL, 4096)) - { - log(fmt::format("CreatePipe error: %d\n", GetLastError())); - // Bye(); - } - hReadPipe = hRp; - hWritePipe = hWp; - hErrorPipe = hEp; - } - - /* fork&exec new process */ - ZeroMemory(&si, sizeof(STARTUPINFO)); - si.cb = sizeof(STARTUPINFO); - si.hStdInput = hReadPipe; - si.hStdOutput = hWritePipe; - si.hStdError = hErrorPipe; - si.dwFlags |= STARTF_USESTDHANDLES; - - if (!CreateProcess(NULL, (LPSTR)name.c_str(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) - { - log(fmt::format("CreateProcess: %d\n", GetLastError())); - // Bye(); - } - - if (useSharedBuffer) - { - /* don't need child's other socket end */ - CloseHandle(hReadPipe); - - /* record pid, io channels, init lp and snoop list */ - setFds(reinterpret_cast(hWritePipe), reinterpret_cast(hWritePipe)); - } - else - { - /* don't need child's side of pipes */ - CloseHandle(hWritePipe); - CloseHandle(hReadPipe); - - /* record pid, io channels, init lp and snoop list */ - setFds(reinterpret_cast(int) hRp, reinterpret_cast(int) hWp); // 修改为使用新的变量名 - } - - CloseHandle(hErrorPipe); - - // Watch pid - this->pid = pi.dwProcessId; - do - { - (this->pidwatcher).pid = (this->pid); - (this->pidwatcher).flags = !!(1); - } while (0); - ev_child_set() this->pidwatcher.set(this->pid); - // this->pidwatcher.start(); - - // Watch input on efd - hEfd = hEp; // 修改为使用新的变量名 - SetHandleInformation(hEfd, HANDLE_FLAG_INHERIT, 0); - this->efd = _open_osfhandle(reinterpret_cast(hEfd), _O_RDONLY | _O_BINARY); - unsigned long mode = 1; - ioctlsocket(this->efd, FIONBIO, &mode); - this->eio.start(this->efd, ev::READ); - - /* first message primes driver to report its properties -- dev known - * if restarting - */ - if (verbose > 0) - DLOG_F(INFO, "pid=%d rfd=%d wfd=%d efd=%d\n", pid, (int)hRp, (int)hWp, (int)hEp); - - XMLEle *root = addXMLEle(NULL, "getProperties"); - addXMLAtt(root, "version", TO_STRING(HYDROGENV)); - mp = new Msg(nullptr, root); - - // pushmsg can kill mp. do at end - pushMsg(mp); -} - -#else -void LocalDvrInfo::start() -{ - Msg *mp; - int rp[2], wp[2], ep[2]; - int ux[2]; - int pid; - -#ifdef OSX_EMBEDED_MODE - fprintf(stderr, "STARTING \"%s\"\n", name.c_str()); - fflush(stderr); -#endif - - /* build three pipes: r, w and error*/ - if (useSharedBuffer) - { - // FIXME: lots of FD are opened by hydrogenserver. FD_CLOEXEC is a must + check other fds - if (socketpair(AF_UNIX, SOCK_STREAM, 0, ux) == -1) - { - log(fmt::format("socketpair: %s\n", strerror(errno))); - // Bye(); - } - } - else - { - if (pipe(rp) < 0) - { - log(fmt::format("read pipe: %s\n", strerror(errno))); - // Bye(); - } - if (pipe(wp) < 0) - { - log(fmt::format("write pipe: %s\n", strerror(errno))); - // Bye(); - } - } - if (pipe(ep) < 0) - { - log(fmt::format("stderr pipe: %s\n", strerror(errno))); - // Bye(); - } - - /* fork&exec new process */ - pid = fork(); - if (pid < 0) - { - log(fmt::format("fork: %s\n", strerror(errno))); - // Bye(); - } - if (pid == 0) - { - /* child: exec name */ - int fd; - - /* rig up pipes */ - if (useSharedBuffer) - { - // For unix socket, the same socket end can be used for both read & write - dup2(ux[0], 0); /* driver stdin reads from ux[0] */ - dup2(ux[0], 1); /* driver stdout writes to ux[0] */ - ::close(ux[0]); - ::close(ux[1]); - } - else - { - dup2(wp[0], 0); /* driver stdin reads from wp[0] */ - dup2(rp[1], 1); /* driver stdout writes to rp[1] */ - } - dup2(ep[1], 2); /* driver stderr writes to e[]1] */ - for (fd = 3; fd < 100; fd++) - (void)::close(fd); - - if (!envDev.empty()) - setenv("HYDROGENDEV", envDev.c_str(), 1); - /* Only reset environment variable in case of FIFO */ - else if (fifo) - unsetenv("HYDROGENDEV"); - if (!envConfig.empty()) - setenv("HYDROGENCONFIG", envConfig.c_str(), 1); - else if (fifo) - unsetenv("HYDROGENCONFIG"); - if (!envSkel.empty()) - setenv("HYDROGENSKEL", envSkel.c_str(), 1); - else if (fifo) - unsetenv("HYDROGENSKEL"); - std::string executable; - if (!envPrefix.empty()) - { - setenv("HYDROGENPREFIX", envPrefix.c_str(), 1); -#if defined(OSX_EMBEDED_MODE) - executable = envPrefix + "/Contents/MacOS/" + name; -#elif defined(__APPLE__) - executable = envPrefix + "/" + name; -#else - executable = envPrefix + "/bin/" + name; -#endif - - fprintf(stderr, "%s\n", executable.c_str()); - - execlp(executable.c_str(), name.c_str(), NULL); - } - else - { - if (name[0] == '.') - { - executable = std::string(dirname((char *)me)) + "/" + name; - execlp(executable.c_str(), name.c_str(), NULL); - } - else - { - execlp(name.c_str(), name.c_str(), NULL); - } - } - -#ifdef OSX_EMBEDED_MODE - fprintf(stderr, "FAILED \"%s\"\n", name.c_str()); - fflush(stderr); -#endif - log(fmt::format("execlp %s: %s\n", executable.c_str(), strerror(errno))); - _exit(1); /* parent will notice EOF shortly */ - } - - if (useSharedBuffer) - { - /* don't need child's other socket end */ - ::close(ux[0]); - - /* record pid, io channels, init lp and snoop list */ - setFds(ux[1], ux[1]); - rp[0] = ux[1]; - wp[1] = ux[1]; - } - else - { - /* don't need child's side of pipes */ - ::close(wp[0]); - ::close(rp[1]); - - /* record pid, io channels, init lp and snoop list */ - setFds(rp[0], wp[1]); - } - - ::close(ep[1]); - - // Watch pid - this->pid = pid; -#ifdef USE_LIBUV - if (this->pidwatcher.loop != nullptr && this->pidwatcher.signal_cb != nullptr) - { - uv_signal_start(&this->pidwatcher, this->pidwatcher.signal_cb, SIGCHLD); - } - -#else - this->pidwatcher.set(pid); - this->pidwatcher.start(); -#endif - - // Watch input on efd - this->efd = ep[0]; - fcntl(this->efd, F_SETFL, fcntl(this->efd, F_GETFL, 0) | O_NONBLOCK); -#ifdef USE_LIBUV - // Start eio file descriptor watcher - if (this->eio.loop != nullptr) - { - uv_poll_start(&this->eio, UV_READABLE, (uv_poll_cb)&LocalDvrInfo::onEfdEvent); - } - -#else - this->eio.start(this->efd, ev::READ); -#endif - - /* first message primes driver to report its properties -- dev known - * if restarting - */ - if (verbose > 0) - DLOG_F(INFO, "pid=%d rfd=%d wfd=%d efd=%d\n", pid, rp[0], wp[1], ep[0]); - - XMLEle *root = addXMLEle(NULL, "getProperties"); - addXMLAtt(root, "version", TO_STRING(HYDROGENV)); - mp = new Msg(nullptr, root); - - // pushmsg can kill mp. do at end - pushMsg(mp); -} -#endif - -LocalDvrInfo *LocalDvrInfo::clone() const -{ - return new LocalDvrInfo(*this); -} - -void LocalDvrInfo::closeEfd() -{ - ::close(efd); - efd = -1; -#ifdef USE_LIBUV - uv_poll_stop(&eio); -#else - eio.stop(); -#endif -} - -void LocalDvrInfo::closePid() -{ - pid = 0; -#ifdef USE_LIBUV - uv_signal_stop(&pidwatcher); -#else - pidwatcher.stop(); -#endif -} - -#ifdef USE_LIBUV - -void LocalDvrInfo::onEfdEvent(uv_poll_t *handle, int status, int events) -{ - if (status < 0) - { - // 处理错误情况 - const char *error = uv_strerror(status); - LOG_F(ERROR, "Error on stderr: {}", error); - closeEfd(); - return; - } - - if (events & UV_READABLE) - { - ssize_t nr; - - /* read more */ - nr = read(efd, errbuff + errbuffpos, sizeof(errbuff) - errbuffpos); - if (nr <= 0) - { - if (nr < 0) - { - if (errno == EAGAIN || errno == EWOULDBLOCK) - return; - - LOG_F(ERROR, "stderr {}", strerror(errno)); - } - else - LOG_F(ERROR, "stderr EOF"); - closeEfd(); - return; - } - errbuffpos += nr; - - for (int i = 0; i < errbuffpos; ++i) - { - if (errbuff[i] == '\n') - { - LOG_F(ERROR, "{}{}", (int)i, errbuff); - i++; /* count including nl */ - errbuffpos -= i; /* remove from nexbuf */ - memmove(errbuff, errbuff + i, errbuffpos); /* slide remaining to front */ - i = -1; /* restart for loop scan */ - } - } - } -} - -#ifdef _WIN32 -void LocalDvrInfo::onPidEvent(uv_signal_t *handle, int exit_status) -#else -void LocalDvrInfo::onPidEvent(uv_process_t *handle, int exit_status, int term_signal) -#endif -{ - // 处理pid事件 - if (exit_status == UV_SIGNAL && handle->status) - { - if (WIFEXITED(pidwatcher.signum)) - { - LOG_F(ERROR, "process {} exited with status {}", pid, WEXITSTATUS(pidwatcher.signum)); - } - else if (WIFSIGNALED(pidwatcher.signum)) - { - int exit_status = WTERMSIG(pidwatcher.signum); - LOG_F(ERROR, "process {} killed with signal {} - {}", pid, exit_status, strsignal(exit_status)); - } - pid = 0; - uv_signal_stop(&pidwatcher); - } -} - -#else -void LocalDvrInfo::onEfdEvent(ev::io &, int revents) -{ - if (EV_ERROR & revents) - { - int sockErrno = readFdError(this->efd); - if (sockErrno) - { - LOG_F(ERROR, "Error on stderr: {}", strerror(sockErrno)); - closeEfd(); - } - return; - } - - if (revents & EV_READ) - { - ssize_t nr; - - /* read more */ - nr = read(efd, errbuff + errbuffpos, sizeof(errbuff) - errbuffpos); - if (nr <= 0) - { - if (nr < 0) - { - if (errno == EAGAIN || errno == EWOULDBLOCK) - return; - - LOG_F(ERROR, "stderr {}", strerror(errno)); - } - else - LOG_F(ERROR, "stderr EOF"); - closeEfd(); - return; - } - errbuffpos += nr; - - for (int i = 0; i < errbuffpos; ++i) - { - if (errbuff[i] == '\n') - { - LOG_F(ERROR, "{}{}", (int)i, errbuff); - i++; /* count including nl */ - errbuffpos -= i; /* remove from nexbuf */ - memmove(errbuff, errbuff + i, errbuffpos); /* slide remaining to front */ - i = -1; /* restart for loop scan */ - } - } - } -} - -#ifdef _WIN32 -void LocalDvrInfo::onPidEvent(ev_child &, int revents) -#else -void LocalDvrInfo::onPidEvent(ev::child &, int revents) -#endif -{ - if (revents & EV_CHILD) - { - if (WIFEXITED(pidwatcher.rstatus)) - { - LOG_F(ERROR, "process {} exited with status {}", pid, WEXITSTATUS(pidwatcher.rstatus)); - } - else if (WIFSIGNALED(pidwatcher.rstatus)) - { - int exit_status = WTERMSIG(pidwatcher.rstatus); - LOG_F(ERROR, "process {} killed with signal {} - {}", pid, exit_status, strsignal(exit_status)); - } - pid = 0; - this->pidwatcher.stop(); - } -} -#endif \ No newline at end of file diff --git a/modules/hydrogen_loader/local_driver.hpp b/modules/hydrogen_loader/local_driver.hpp deleted file mode 100644 index 41039ebf..00000000 --- a/modules/hydrogen_loader/local_driver.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "driver_info.hpp" - -class LocalDvrInfo : public DvrInfo -{ - char errbuff[1024]; /* buffer for stderr pipe. line too long will be clipped */ - int errbuffpos = 0; /* first free pos in buffer */ -#ifdef USE_LIBUV - uv_poll_t eio; // 替换 ev::io eio; - void onEfdEvent(uv_poll_t *handle, int status, int events); // 替换 void onEfdEvent(ev::io &watcher, int revents); - uv_signal_t pidwatcher; // 替换 ev::child pidwatcher; - void onPidEvent(uv_process_t *handle, int exit_status, int term_signal); // 替换 void onPidEvent(ev_child &watcher, int revents); -#else - ev::io eio; /* Event loop io events */ - void onEfdEvent(ev::io &watcher, int revents); /* callback for data on efd */ - -#ifdef _WIN32 - ev_child pidwatcher; -#else - ev::child pidwatcher; -#endif - -#ifdef _WIN32 - void onPidEvent(ev_child &watcher, int revents); -#else - void onPidEvent(ev::child &watcher, int revents); -#endif -#endif - - int pid = 0; /* process id or 0 for N/A (not started/terminated) */ - int efd = -1; /* stderr from driver, or -1 when N/A */ - - void closeEfd(); - void closePid(); - -protected: - LocalDvrInfo(const LocalDvrInfo &model); - -public: - std::string envDev; - std::string envConfig; - std::string envSkel; - std::string envPrefix; - - LocalDvrInfo(); - virtual ~LocalDvrInfo(); - - virtual void start(); - - virtual LocalDvrInfo *clone() const; - - virtual const std::string remoteServerUid() const - { - return ""; - } -}; \ No newline at end of file diff --git a/modules/hydrogen_loader/message.cpp b/modules/hydrogen_loader/message.cpp deleted file mode 100644 index d918d15b..00000000 --- a/modules/hydrogen_loader/message.cpp +++ /dev/null @@ -1,272 +0,0 @@ -/* - * message.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: Message - -**************************************************/ - -#include "message.hpp" - -#include -#include - -#include "lilxml.hpp" - -#include "message_queue.hpp" -#include "xml_util.hpp" - -#include "atom/log/loguru.hpp" - -MsgChunck::MsgChunck() : sharedBufferIdsToAttach() -{ - content = nullptr; - contentLength = 0; -} - -MsgChunck::MsgChunck(char *content, unsigned long length) : sharedBufferIdsToAttach() -{ - this->content = content; - this->contentLength = length; -} - -Msg::Msg(MsgQueue *from, XMLEle *ele) : sharedBuffers() -{ - this->from = from; - xmlContent = ele; - hasInlineBlobs = false; - hasSharedBufferBlobs = false; - - convertionToSharedBuffer = nullptr; - convertionToInline = nullptr; - - queueSize = sprlXMLEle(xmlContent, 0); - for (auto blobContent : findBlobElements(xmlContent)) - { - std::string attached = findXMLAttValu(blobContent, "attached"); - if (attached == "true") - { - hasSharedBufferBlobs = true; - } - else - { - hasInlineBlobs = true; - } - } -} - -Msg::~Msg() -{ - // Assume convertionToSharedBlob and convertionToInlineBlob were already droped - assert(convertionToSharedBuffer == nullptr); - assert(convertionToInline == nullptr); - - releaseXmlContent(); - releaseSharedBuffers(std::set()); -} - -void Msg::releaseSerialization(SerializedMsg *msg) -{ - if (msg == convertionToSharedBuffer) - { - convertionToSharedBuffer = nullptr; - } - - if (msg == convertionToInline) - { - convertionToInline = nullptr; - } - - delete (msg); - prune(); -} - -void Msg::releaseXmlContent() -{ - if (xmlContent != nullptr) - { - delXMLEle(xmlContent); - xmlContent = nullptr; - } -} - -void Msg::releaseSharedBuffers(const std::set &keep) -{ - for (std::size_t i = 0; i < sharedBuffers.size(); ++i) - { - auto fd = sharedBuffers[i]; - if (fd != -1 && keep.find(fd) == keep.end()) - { - if (close(fd) == -1) - { - perror("Releasing shared buffer"); - } - sharedBuffers[i] = -1; - } - } -} - -void Msg::prune() -{ - // Collect ressources required. - SerializationRequirement req; - if (convertionToSharedBuffer) - { - convertionToSharedBuffer->collectRequirements(req); - } - if (convertionToInline) - { - convertionToInline->collectRequirements(req); - } - // Free the resources. - if (!req.xml) - { - releaseXmlContent(); - } - - releaseSharedBuffers(req.sharedBuffers); - - // Nobody cares anymore ? - if (convertionToSharedBuffer == nullptr && convertionToInline == nullptr) - { - delete (this); - } -} - -bool parseBlobSize(XMLEle *blobWithAttachedBuffer, ssize_t &size) -{ - std::string sizeStr = findXMLAttValu(blobWithAttachedBuffer, "size"); - if (sizeStr == "") - { - return false; - } - std::size_t pos; - size = std::stoll(sizeStr, &pos, 10); - if (pos != sizeStr.size()) - { - LOG_F(ERROR, "Invalid size attribute value %s", sizeStr.c_str()); - return false; - } - return true; -} - -/** Init a message from xml content & additional incoming buffers */ -bool Msg::fetchBlobs(std::list &incomingSharedBuffers) -{ - /* Consume every buffers */ - for (auto blobContent : findBlobElements(xmlContent)) - { - ssize_t blobSize; - if (!parseBlobSize(blobContent, blobSize)) - { - LOG_F(ERROR, "Attached blob misses the size attribute"); - return false; - } - - std::string attached = findXMLAttValu(blobContent, "attached"); - if (attached == "true") - { - if (incomingSharedBuffers.empty()) - { - LOG_F(ERROR, "Missing shared buffer...\n"); - return false; - } - - queueSize += blobSize; - // LOG_F(ERROR,"Found one fd !\n"); - int fd = *incomingSharedBuffers.begin(); - incomingSharedBuffers.pop_front(); - - sharedBuffers.push_back(fd); - } - else - { - // Check cdata length vs blobSize ? - } - } - return true; -} - -void Msg::queuingDone() -{ - prune(); -} - -Msg *Msg::fromXml(MsgQueue *from, XMLEle *root, std::list &incomingSharedBuffers) -{ - Msg *m = new Msg(from, root); - if (!m->fetchBlobs(incomingSharedBuffers)) - { - delete (m); - return nullptr; - } - return m; -} - -SerializedMsg *Msg::buildConvertionToSharedBuffer() -{ - if (convertionToSharedBuffer) - { - return convertionToSharedBuffer; - } - - convertionToSharedBuffer = new SerializedMsgWithSharedBuffer(this); - if (hasInlineBlobs && from) - { - convertionToSharedBuffer->blockReceiver(from); - } - return convertionToSharedBuffer; -} - -SerializedMsg *Msg::buildConvertionToInline() -{ - if (convertionToInline) - { - return convertionToInline; - } - - return convertionToInline = new SerializedMsgWithoutSharedBuffer(this); -} - -SerializedMsg *Msg::serialize(MsgQueue *to) -{ - if (hasSharedBufferBlobs || hasInlineBlobs) - { - if (to->acceptSharedBuffers()) - { - return buildConvertionToSharedBuffer(); - } - else - { - return buildConvertionToInline(); - } - } - else - { - // Just serialize using copy - return buildConvertionToInline(); - } -} \ No newline at end of file diff --git a/modules/hydrogen_loader/message.hpp b/modules/hydrogen_loader/message.hpp deleted file mode 100644 index c565f2e1..00000000 --- a/modules/hydrogen_loader/message.hpp +++ /dev/null @@ -1,160 +0,0 @@ -/* - * message.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: Message - -**************************************************/ - -#pragma once - -#include -#include -#include - -#include "lilxml.hpp" - -#include "serialize.hpp" - -class MsgChunckIterator; -/** - * A MsgChunk is either: - * a raw xml fragment - * a ref to a shared buffer in the message - */ -class MsgChunck -{ - friend class SerializedMsg; - friend class SerializedMsgWithSharedBuffer; - friend class SerializedMsgWithoutSharedBuffer; - friend class MsgChunckIterator; - - MsgChunck(); - MsgChunck(char *content, unsigned long length); - - char *content; - unsigned long contentLength; - - std::vector sharedBufferIdsToAttach; -}; - -class MsgChunckIterator -{ - friend class SerializedMsg; - std::size_t chunckId; - unsigned long chunckOffset; - bool endReached; - -public: - MsgChunckIterator() - { - reset(); - } - - // Point to start of message. - void reset() - { - chunckId = 0; - chunckOffset = 0; - // No risk of 0 length message, so always false here - endReached = false; - } - - bool done() const - { - return endReached; - } -}; - -class Msg -{ - friend class SerializedMsg; - friend class SerializedMsgWithSharedBuffer; - friend class SerializedMsgWithoutSharedBuffer; - -private: - // Present for sure until message queing is doned. Prune asap then - XMLEle *xmlContent; - - // Present until message was queued. - MsgQueue *from; - - int queueSize; - bool hasInlineBlobs; - bool hasSharedBufferBlobs; - - std::vector sharedBuffers; /* fds of shared buffer */ - - // Convertion task and resultat of the task - SerializedMsg *convertionToSharedBuffer; - SerializedMsg *convertionToInline; - - SerializedMsg *buildConvertionToSharedBuffer(); - SerializedMsg *buildConvertionToInline(); - - bool fetchBlobs(std::list &incomingSharedBuffers); - - void releaseXmlContent(); - void releaseSharedBuffers(const std::set &keep); - - // Remove resources that can be removed. - // Will be called when queuingDone is true and for every change of staus from convertionToXXX - void prune(); - - void releaseSerialization(SerializedMsg *form); - - ~Msg(); - -public: - /* Message will not be queued anymore. Release all possible resources, incl self */ - void queuingDone(); - - Msg(MsgQueue *from, XMLEle *root); - - static Msg *fromXml(MsgQueue *from, XMLEle *root, std::list &incomingSharedBuffers); - - /** - * Handle multiple cases: - * - * - inline => attached. - * Exceptional. The inline is already in memory within xml. It must be converted to shared buffer async. - * FIXME: The convertion should block the emitter. - * - * - attached => attached - * Default case. No convertion is required. - * - * - inline => inline - * Frequent on system not supporting attachment. - * - * - attached => inline - * Frequent. The convertion will be made during write. The convert/write must be offshored to a dedicated thread. - * - * The returned AsyncTask will be ready once "to" can write the message - */ - SerializedMsg *serialize(MsgQueue *from); -}; - -bool parseBlobSize(XMLEle *blobWithAttachedBuffer, ssize_t &size); \ No newline at end of file diff --git a/modules/hydrogen_loader/message_queue.cpp b/modules/hydrogen_loader/message_queue.cpp deleted file mode 100644 index e24214de..00000000 --- a/modules/hydrogen_loader/message_queue.cpp +++ /dev/null @@ -1,900 +0,0 @@ -#include "message_queue.hpp" - -#ifdef _WIN32 -#include // 包含 WinSock2 库 -#include // 包含 MSWSock 库 -#include -#include -#else -#include -#endif - -#include -#include - -#include "io.hpp" -#include "hydrogen_server.hpp" - -#include "atom/log/loguru.hpp" - -MsgQueue::MsgQueue(bool useSharedBuffer) : useSharedBuffer(useSharedBuffer) -{ - lp = newLilXML(); -#ifdef USE_LIBUV - uv_poll_init(uv_default_loop(), &rio, rFd); - uv_poll_init(uv_default_loop(), &wio, wFd); - rio.data = this; - wio.data = this; -#else - rio.set(this); - wio.set(this); -#endif - rFd = -1; - wFd = -1; -} - -MsgQueue::~MsgQueue() -{ -#ifdef USE_LIBUV - uv_poll_stop(&rio); - uv_poll_stop(&wio); -#else - rio.stop(); - wio.stop(); -#endif - - clearMsgQueue(); - delLilXML(lp); - lp = nullptr; - - setFds(-1, -1); - - /* unreference messages queue for this client */ - auto msgqcp = msgq; - msgq.clear(); - for (auto mp : msgqcp) - { - mp->release(this); - } -} - -void MsgQueue::closeWritePart() -{ - if (wFd == -1) - { - // Already closed - return; - } - - int oldWFd = wFd; - - wFd = -1; - // Clear the queue and stop the io slot - clearMsgQueue(); - - if (oldWFd == rFd) - { -#ifdef _WIN32 - if (shutdown(static_cast(oldWFd), SD_SEND) == SOCKET_ERROR) -#else - if (shutdown(oldWFd, SHUT_WR) == -1) -#endif - { - if (errno != ENOTCONN) - { - LOG_F(ERROR, "socket shutdown failed: %s\n", strerror(errno)); - close(); - } - } - } - else - { - if (::close(oldWFd) == -1) - { - // log(fmt("socket close failed: %s\n", strerror(errno))); - close(); - } - } -} - -#ifdef USE_LIBUV -void MsgQueue::setFds(int rFd, int wFd) -{ - if (this->rFd != -1) - { - uv_poll_stop(&rio); - uv_poll_stop(&wio); - ::close(this->rFd); - if (this->rFd != this->wFd) - { - ::close(this->wFd); - } - } - else if (this->wFd != -1) - { - uv_poll_stop(&wio); - ::close(this->wFd); - } - - this->rFd = rFd; - this->wFd = wFd; - this->nsent.reset(); - - if (rFd != -1) - { -#ifdef _WIN32 - int fd = _fileno(reinterpret_cast(_get_osfhandle(rFd))); - _setmode(fd, _O_BINARY); -#else - fcntl(rFd, F_SETFL, fcntl(rFd, F_GETFL, 0) | O_NONBLOCK); -#endif - - if (wFd != rFd) - { -#ifdef _WIN32 - int fd = _fileno(reinterpret_cast(_get_osfhandle(wFd))); - _setmode(fd, _O_BINARY); -#else - fcntl(wFd, F_SETFL, fcntl(wFd, F_GETFL, 0) | O_NONBLOCK); -#endif - } - - uv_poll_init(uv_default_loop(), &rio, rFd); - uv_poll_init(uv_default_loop(), &wio, wFd); - rio.data = this; - wio.data = this; - updateIos(); - } -} -#else -void MsgQueue::setFds(int rFd, int wFd) -{ - if (this->rFd != -1) - { - rio.stop(); - wio.stop(); - ::close(this->rFd); - if (this->rFd != this->wFd) - { - ::close(this->wFd); - } - } - else if (this->wFd != -1) - { - wio.stop(); - ::close(this->wFd); - } - - this->rFd = rFd; - this->wFd = wFd; - this->nsent.reset(); - - if (rFd != -1) - { -#ifdef _WIN32 - int fd = _fileno(reinterpret_cast(_get_osfhandle(rFd))); - _setmode(fd, _O_BINARY); -#else - fcntl(rFd, F_SETFL, fcntl(rFd, F_GETFL, 0) | O_NONBLOCK); -#endif - - if (wFd != rFd) - { -#ifdef _WIN32 - int fd = _fileno(reinterpret_cast(_get_osfhandle(wFd))); - _setmode(fd, _O_BINARY); -#else - fcntl(wFd, F_SETFL, fcntl(wFd, F_GETFL, 0) | O_NONBLOCK); -#endif - } - - rio.set(rFd, ev::READ); - wio.set(wFd, ev::WRITE); - updateIos(); - } -} -#endif - -SerializedMsg *MsgQueue::headMsg() const -{ - if (msgq.empty()) - return nullptr; - return *(msgq.begin()); -} - -void MsgQueue::consumeHeadMsg() -{ - auto msg = headMsg(); - msgq.pop_front(); - msg->release(this); - nsent.reset(); - - updateIos(); -} - -void MsgQueue::pushMsg(Msg *mp) -{ - // Don't write messages to client that have been disconnected - if (wFd == -1) - { - return; - } - - auto serialized = mp->serialize(this); - - msgq.push_back(serialized); - serialized->addAwaiter(this); - - // Register for client write - updateIos(); -} - -#ifdef USE_LIBUV -void MsgQueue::updateIos() -{ - if (wFd != -1) - { - if (msgq.empty() || !msgq.front()->requestContent(nsent)) - { - uv_poll_stop(&wio); - } - else - { - uv_poll_start(&wio, UV_WRITABLE, IOCallback); - } - } - if (rFd != -1) - { - uv_poll_start(&rio, UV_READABLE, IOCallback); - } -} - -// IO事件回调函数 -void MsgQueue::IOCallback(uv_poll_t *handle, int status, int events) -{ - MsgQueue *msgQueue = static_cast(handle->data); - if (status < 0) - { - // 处理错误情况 - return; - } - - if (events & UV_READABLE) - { - // 处理可读事件 - } - - if (events & UV_WRITABLE) - { - // 处理可写事件 - } -} - -#else -void MsgQueue::updateIos() -{ - if (wFd != -1) - { - if (msgq.empty() || !msgq.front()->requestContent(nsent)) - { - wio.stop(); - } - else - { - wio.start(); - } - } - if (rFd != -1) - { - rio.start(); - } -} -#endif - -void MsgQueue::messageMayHaveProgressed(const SerializedMsg *msg) -{ - if ((!msgq.empty()) && (msgq.front() == msg)) - { - updateIos(); - } -} - -void MsgQueue::clearMsgQueue() -{ - nsent.reset(); - - auto queueCopy = msgq; - for (auto mp : queueCopy) - { - mp->release(this); - } - msgq.clear(); - - // Cancel io write events - updateIos(); -#ifdef USE_LIBUV - uv_poll_stop(&wio); -#else - wio.stop(); -#endif -} - -unsigned long MsgQueue::msgQSize() const -{ - unsigned long l = 0; - - for (auto mp : msgq) - { - l += sizeof(Msg); - l += mp->queueSize(); - } - - return (l); -} - -#ifdef USE_LIBUV -void MsgQueue::ioCb(uv_poll_t *handle, int status, int revents) -{ - if (status < 0) - { - int sockErrno = readFdError(this->rFd); - if ((!sockErrno) && this->wFd != this->rFd) - { - sockErrno = readFdError(this->wFd); - } - - if (sockErrno) - { - // log(fmt("Communication error: %s\n", strerror(sockErrno))); - close(); - return; - } - } - - if (UV_READABLE & revents) - readFromFd(); - - if (UV_WRITABLE & revents) - writeToFd(); -} - -void MsgQueue::setReadWriteCallback() -{ - uv_poll_init(uv_default_loop(), &rio, rFd); - uv_poll_init(uv_default_loop(), &wio, wFd); - rio.data = this; - wio.data = this; - uv_poll_start(&rio, UV_READABLE, IOCallback); - uv_poll_start(&wio, UV_WRITABLE, IOCallback); -} -#else -void MsgQueue::ioCb(ev::io &, int revents) -{ - if (EV_ERROR & revents) - { - int sockErrno = readFdError(this->rFd); - if ((!sockErrno) && this->wFd != this->rFd) - { - sockErrno = readFdError(this->wFd); - } - - if (sockErrno) - { - // log(fmt("Communication error: %s\n", strerror(sockErrno))); - close(); - return; - } - } - - if (revents & EV_READ) - readFromFd(); - - if (revents & EV_WRITE) - writeToFd(); -} -#endif - -#ifdef _WIN32 -size_t MsgQueue::doRead(char *buf, size_t nr) -{ - if (!useSharedBuffer) - { - // 非共享缓冲区方式 - DWORD bytesToRead = static_cast(nr); - DWORD bytesRead = 0; - if (!ReadFile(reinterpret_cast(rFd), buf, bytesToRead, &bytesRead, NULL)) - { - // 错误处理 - return -1; - } - return bytesRead; - } - else - { - // 使用共享缓冲区方式 - WSABUF wsaBuf{nr, buf}; - DWORD bytesReceived = 0; - DWORD flags = 0; - WSAMSG wsaMsg; - memset(&wsaMsg, 0x0, sizeof(wsaMsg)); - wsaMsg.name = nullptr; - wsaMsg.namelen = 0; - wsaMsg.lpBuffers = &wsaBuf; - wsaMsg.dwBufferCount = 1; - - std::vector controlBuffer(sizeof(WSACMSGHDR) + WSA_CMSG_SPACE(MAXFD_PER_MESSAGE * sizeof(HANDLE))); - wsaMsg.Control.len = static_cast(controlBuffer.size()); - wsaMsg.Control.buf = controlBuffer.data(); - - LPFN_WSARECVMSG pfnWSARecvMsg = nullptr; - GUID guidWSARecvMsg = WSAID_WSARECVMSG; - DWORD dwBytes = 0; - SOCKET sock; - if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guidWSARecvMsg, sizeof(guidWSARecvMsg), - &pfnWSARecvMsg, sizeof(pfnWSARecvMsg), &dwBytes, nullptr, nullptr) != 0) - { - // 错误处理 - return -1; - } - - int result = pfnWSARecvMsg(sock, &wsaMsg, &bytesReceived, nullptr, nullptr); - if (result == SOCKET_ERROR) - { - // 错误处理 - return -1; - } - - for (char *pData = (wsaMsg.Control.buf); pData < (wsaMsg.Control.buf + wsaMsg.Control.len);) - { - WSACMSGHDR *pHdr = reinterpret_cast(pData); - if (pHdr->cmsg_level == SOL_SOCKET) - { - DWORD fdCount = 0; - while (WSA_CMSG_NXTHDR(&wsaMsg, pHdr) != nullptr) - { - pHdr = WSA_CMSG_NXTHDR(&wsaMsg, pHdr); - if (pHdr->cmsg_level == SOL_SOCKET) - { - fdCount++; - } - } - - int *pfds = reinterpret_cast(WSA_CMSG_DATA(pHdr)); - for (int i = 0; i < fdCount; ++i) - { - incomingSharedBuffers.push_back(reinterpret_cast(static_cast(pfds[i]))); - } - } - pData += pHdr->cmsg_len; - } - return bytesReceived; - } -} -#else -size_t MsgQueue::doRead(char *buf, size_t nr) -{ - if (!useSharedBuffer) - { - /* read client - works for all kinds of fds incl pipe*/ - return read(rFd, buf, sizeof(buf)); - } - else - { - // Use recvmsg for ancillary data - struct msghdr msgh; - struct iovec iov; - - union - { - struct cmsghdr cmsgh; - /* Space large enough to hold an 'int' */ - char control[CMSG_SPACE(MAXFD_PER_MESSAGE * sizeof(int))]; - } control_un; - - iov.iov_base = buf; - iov.iov_len = nr; - - msgh.msg_name = NULL; - msgh.msg_namelen = 0; - msgh.msg_iov = &iov; - msgh.msg_iovlen = 1; - msgh.msg_flags = 0; - msgh.msg_control = control_un.control; - msgh.msg_controllen = sizeof(control_un.control); - - int recvflag; -#ifdef __linux__ - recvflag = MSG_CMSG_CLOEXEC; -#else - recvflag = 0; -#endif - int size = recvmsg(rFd, &msgh, recvflag); - if (size == -1) - { - return -1; - } - - for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg)) - { - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) - { - int fdCount = 0; - while (cmsg->cmsg_len >= CMSG_LEN((fdCount + 1) * sizeof(int))) - { - fdCount++; - } - // // log(fmt("Received %d fds\n", fdCount)); - int *fds = (int *)CMSG_DATA(cmsg); - for (int i = 0; i < fdCount; ++i) - { -#ifndef __linux__ - fcntl(fds[i], F_SETFD, FD_CLOEXEC); -#endif - incomingSharedBuffers.push_back(fds[i]); - } - } - else - { - // log(fmt("Ignoring ancillary data level %d, type %d\n", cmsg->cmsg_level, cmsg->cmsg_type)); - } - } - return size; - } -} -#endif - -void MsgQueue::readFromFd() -{ - char buf[MAXRBUF]; - ssize_t nr; - - /* read client */ - nr = doRead(buf, sizeof(buf)); - if (nr <= 0) - { - if (errno == EAGAIN || errno == EWOULDBLOCK) - return; - - if (nr < 0) - LOG_F(ERROR, "read: %s\n", strerror(errno)); - else if (verbose > 0) - LOG_F(ERROR, "read EOF\n"); - close(); - return; - } - - /* process XML chunk */ - char err[1024]; - XMLEle **nodes = parseXMLChunk(lp, buf, nr, err); - if (!nodes) - { - // log(fmt("XML error: %s\n", err)); - // log(fmt("XML read: %.*s\n", (int)nr, buf)); - close(); - return; - } - - int inode = 0; - - XMLEle *root = nodes[inode]; - // Stop processing message in case of deletion... - auto hb = heartBeat(); - while (root) - { - if (hb.alive()) - { - if (verbose > 2) - traceMsg("read ", root); - else if (verbose > 1) - { - DLOG_F(INFO, "read <%s device='%s' name='%s'>\n", tagXMLEle(root), findXMLAttValu(root, "device"), findXMLAttValu(root, "name")); - } - - onMessage(root, incomingSharedBuffers); - } - else - { - // Otherwise, client got killed. Just release pending messages - delXMLEle(root); - } - inode++; - root = nodes[inode]; - } - - free(nodes); -} - -#ifdef _WIN32 -void MsgQueue::writeToFd() -{ - ssize_t nw; - void *data; - ssize_t nsend; - std::vector sharedBuffers; - - // 获取当前消息 - auto mp = headMsg(); - if (mp == nullptr) - { - LOG_F(ERROR, "Unexpected write notification"); - return; - } - - do - { - if (!mp->getContent(nsent, data, nsend, sharedBuffers)) - { - wio.stop(); - return; - } - - if (nsend == 0) - { - consumeHeadMsg(); - mp = headMsg(); - if (mp == nullptr) - { - return; - } - } - } while (nsend == 0); - - // 发送下一个块,不超过MAXWSIZ以减少阻塞 - if (nsend > MAXWSIZ) - nsend = MAXWSIZ; - - if (!useSharedBuffer) - { - nw = write(static_cast(wFd), reinterpret_cast(data), static_cast(nsend)); - } - else - { - WSAMSG msgh{}; - WSABUF iov[1]{}; - int cmsghdrlength; - std::vector controlBuffer; - - int fdCount = sharedBuffers.size(); - if (fdCount > 0) - { - if (fdCount > MAXFD_PER_MESSAGE) - { - LOG_F(ERROR, "attempt to send too many FD\n"); - close(); - return; - } - - cmsghdrlength = WSA_CMSG_SPACE(fdCount * sizeof(int)); - controlBuffer.resize(cmsghdrlength); - - // 填充控制信息 - msgh.Control.buf = controlBuffer.data(); - msgh.Control.len = cmsghdrlength; - LPWSACMSGHDR pCmsgHdr = WSA_CMSG_FIRSTHDR(&msgh); - pCmsgHdr->cmsg_level = SOL_SOCKET; - pCmsgHdr->cmsg_len = cmsghdrlength; - - int *fdPtr = reinterpret_cast(WSA_CMSG_DATA(pCmsgHdr)); - for (int i = 0; i < fdCount; ++i) - { - fdPtr[i] = sharedBuffers[i]; - } - } - else - { - cmsghdrlength = 0; - msgh.Control.buf = nullptr; - msgh.Control.len = cmsghdrlength; - } - - iov[0].buf = reinterpret_cast(data); - iov[0].len = static_cast(nsend); - - msgh.dwFlags = 0; - msgh.name = nullptr; - msgh.namelen = 0; - msgh.lpBuffers = iov; - msgh.dwBufferCount = 1; - - nw = WSASendMsg(static_cast(wFd), &msgh, 0, nullptr, nullptr, nullptr); - - controlBuffer.clear(); - } - - // 发生错误时关闭写入部分 - if (nw <= 0) - { - if (nw == 0) - DLOG_F(INFO, "write returned 0\n"); - else - LOG_F(ERROR, "write: %s\n", strerror(errno)); - - // 保持读取部分打开 - closeWritePart(); - return; - } - - // 更新发送量,完成后释放消息并从队列中弹出 - mp->advance(nsent, nw); - if (nsent.done()) - consumeHeadMsg(); -} - -#else -void MsgQueue::writeToFd() -{ - ssize_t nw; - void *data; - ssize_t nsend; - std::vector sharedBuffers; - - /* get current message */ - auto mp = headMsg(); - if (mp == nullptr) - { - LOG_F(ERROR, "Unexpected write notification"); - return; - } - - do - { - if (!mp->getContent(nsent, data, nsend, sharedBuffers)) - { -#ifdef USE_LIBUV - uv_poll_stop(&wio); -#else - wio.stop(); -#endif - return; - } - - if (nsend == 0) - { - consumeHeadMsg(); - mp = headMsg(); - if (mp == nullptr) - { - return; - } - } - } while (nsend == 0); - - /* send next chunk, never more than MAXWSIZ to reduce blocking */ - if (nsend > MAXWSIZ) - nsend = MAXWSIZ; - - if (!useSharedBuffer) - { - nw = write(wFd, data, nsend); - } - else - { - struct msghdr msgh; - struct iovec iov[1]; - int cmsghdrlength; - struct cmsghdr *cmsgh; - - int fdCount = sharedBuffers.size(); - if (fdCount > 0) - { - if (fdCount > MAXFD_PER_MESSAGE) - { - LOG_F(ERROR, "attempt to send too many FD\n"); - close(); - return; - } - - cmsghdrlength = CMSG_SPACE((fdCount * sizeof(int))); - // FIXME: abort on alloc error here - cmsgh = (struct cmsghdr *)malloc(cmsghdrlength); - memset(cmsgh, 0, cmsghdrlength); - - /* Write the fd as ancillary data */ - cmsgh->cmsg_len = CMSG_LEN(fdCount * sizeof(int)); - cmsgh->cmsg_level = SOL_SOCKET; - cmsgh->cmsg_type = SCM_RIGHTS; - msgh.msg_control = cmsgh; - msgh.msg_controllen = cmsghdrlength; - for (int i = 0; i < fdCount; ++i) - { - ((int *)CMSG_DATA(CMSG_FIRSTHDR(&msgh)))[i] = sharedBuffers[i]; - } - } - else - { - cmsgh = NULL; - cmsghdrlength = 0; - msgh.msg_control = cmsgh; - msgh.msg_controllen = cmsghdrlength; - } - - iov[0].iov_base = data; - iov[0].iov_len = nsend; - - msgh.msg_flags = 0; - msgh.msg_name = NULL; - msgh.msg_namelen = 0; - msgh.msg_iov = iov; - msgh.msg_iovlen = 1; - - nw = sendmsg(wFd, &msgh, MSG_NOSIGNAL); - - free(cmsgh); - } - - /* shut down if trouble */ - if (nw <= 0) - { - if (nw == 0) - DLOG_F(INFO, "write returned 0\n"); - else - LOG_F(ERROR, "write: %s\n", strerror(errno)); - - // Keep the read part open - closeWritePart(); - return; - } - - /* update amount sent. when complete: free message if we are the last - * to use it and pop from our queue. - */ - mp->advance(nsent, nw); - if (nsent.done()) - consumeHeadMsg(); -} -#endif - -void MsgQueue::crackBLOB(const char *enableBLOB, BLOBHandling *bp) -{ - if (!strcmp(enableBLOB, "Also")) - *bp = B_ALSO; - else if (!strcmp(enableBLOB, "Only")) - *bp = B_ONLY; - else if (!strcmp(enableBLOB, "Never")) - *bp = B_NEVER; -} - -void MsgQueue::traceMsg(const std::string &logMsg, XMLEle *root) -{ - DLOG_F(INFO, "{}", logMsg); - - static const char *prtags[] = - { - "defNumber", - "oneNumber", - "defText", - "oneText", - "defSwitch", - "oneSwitch", - "defLight", - "oneLight", - }; - XMLEle *e; - const char *msg, *perm, *pcd; - unsigned int i; - - /* print tag header */ - // fprintf(stderr, "%s %s %s %s", tagXMLEle(root), findXMLAttValu(root, "device"), findXMLAttValu(root, "name"), - // findXMLAttValu(root, "state")); - LOG_F(ERROR, "{} {} {} {}", tagXMLEle(root), findXMLAttValu(root, "device"), findXMLAttValu(root, "name"), - findXMLAttValu(root, "state")); - pcd = pcdataXMLEle(root); - if (pcd[0]) - // fprintf(stderr, " %s", pcd); - LOG_F(ERROR, "{}", pcd); - perm = findXMLAttValu(root, "perm"); - if (perm[0]) - // fprintf(stderr, " %s", perm); - LOG_F(ERROR, "{}", perm); - msg = findXMLAttValu(root, "message"); - if (msg[0]) - // fprintf(stderr, " '%s'", msg); - LOG_F(ERROR, "{}", msg); - - /* print each array value */ - for (e = nextXMLEle(root, 1); e; e = nextXMLEle(root, 0)) - for (i = 0; i < sizeof(prtags) / sizeof(prtags[0]); i++) - if (strcmp(prtags[i], tagXMLEle(e)) == 0) - // fprintf(stderr, "\n %10s='%s'", findXMLAttValu(e, "name"), pcdataXMLEle(e)); - LOG_F(ERROR, "{:<10}='{}'", findXMLAttValu(e, "name"), pcdataXMLEle(e)); -} \ No newline at end of file diff --git a/modules/hydrogen_loader/message_queue.hpp b/modules/hydrogen_loader/message_queue.hpp deleted file mode 100644 index c4dd6732..00000000 --- a/modules/hydrogen_loader/message_queue.hpp +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once - -#include -#include - -#include "lilxml.hpp" -#include "hydrogendevapi.hpp" - -#include "message.hpp" -#include "concurrent.hpp" - -#ifdef _WIN32 -#include -#endif - -#ifdef USE_LIBUV -#include -#else -#include -#endif - -class MsgQueue : public Collectable -{ - int rFd, wFd; - LilXML *lp; /* XML parsing context */ -#ifdef USE_LIBUV - uv_poll_t rio, wio; // Event loop io events - void ioCb(uv_poll_t *handle, int status, int revents); - static void IOCallback(uv_poll_t* handle, int status, int events); - void setReadWriteCallback(); -#else - ev::io rio, wio; /* Event loop io events */ - void ioCb(ev::io &watcher, int revents); -#endif - - // Update the status of FD read/write ability - void updateIos(); - - std::set readBlocker; /* The message that block this queue */ - - std::list msgq; /* To send msg queue */ -#ifdef _WIN32 - std::list incomingSharedBuffers; -#else - std::list incomingSharedBuffers; /* During reception, fds accumulate here */ -#endif - - // Position in the head message - MsgChunckIterator nsent; - - // Handle fifo or socket case - size_t doRead(char *buff, size_t len); - void readFromFd(); - - /* write the next chunk of the current message in the queue to the given - * client. pop message from queue when complete and free the message if we are - * the last one to use it. shut down this client if trouble. - */ - void writeToFd(); - -protected: - bool useSharedBuffer; - int getRFd() const - { - return rFd; - } - int getWFd() const - { - return wFd; - } - - /* print key attributes and values of the given xml to stderr. */ - void traceMsg(const std::string &log, XMLEle *root); - - /* Close the connection. (May be restarted later depending on driver logic) */ - virtual void close() = 0; - - /* Close the writing part of the connection. By default, shutdown the write part, but keep on reading. May delete this */ - virtual void closeWritePart(); - - /* Handle a message. root will be freed by caller. fds of buffers will be closed, unless set to -1 */ - virtual void onMessage(XMLEle *root, std::list &sharedBuffers) = 0; - - /* convert the string value of enableBLOB to our B_ state value. - * no change if unrecognized - */ - static void crackBLOB(const char *enableBLOB, BLOBHandling *bp); - - MsgQueue(bool useSharedBuffer); - -public: - virtual ~MsgQueue(); - - void pushMsg(Msg *msg); - - /* return storage size of all Msqs on the given q */ - unsigned long msgQSize() const; - - SerializedMsg *headMsg() const; - void consumeHeadMsg(); - - /* Remove all messages from queue */ - void clearMsgQueue(); - - void messageMayHaveProgressed(const SerializedMsg *msg); - - void setFds(int rFd, int wFd); - - virtual bool acceptSharedBuffers() const - { - return useSharedBuffer; - } -}; \ No newline at end of file diff --git a/modules/hydrogen_loader/property.cpp b/modules/hydrogen_loader/property.cpp deleted file mode 100644 index 81d0d493..00000000 --- a/modules/hydrogen_loader/property.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/* - * property.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: Property - -**************************************************/ \ No newline at end of file diff --git a/modules/hydrogen_loader/property.hpp b/modules/hydrogen_loader/property.hpp deleted file mode 100644 index 491696cf..00000000 --- a/modules/hydrogen_loader/property.hpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * property.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: Property - -**************************************************/ - -#pragma once -#include - -#include "hydrogendevapi.hpp" - -/* device + property name */ -class Property -{ -public: - std::string dev; - std::string name; - BLOBHandling blob = B_NEVER; /* when to snoop BLOBs */ - - Property(const std::string &dev, const std::string &name) : dev(dev), name(name) {} -}; \ No newline at end of file diff --git a/modules/hydrogen_loader/remote_driver.cpp b/modules/hydrogen_loader/remote_driver.cpp deleted file mode 100644 index e88781ec..00000000 --- a/modules/hydrogen_loader/remote_driver.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include "remote_driver.hpp" - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#include -#endif - -#include "atom/log/loguru.hpp" - -#include "hydrogen_server.hpp" - -#include "atom/log/loguru.hpp" - -RemoteDvrInfo::RemoteDvrInfo() : DvrInfo(false) -{ -} - -RemoteDvrInfo::RemoteDvrInfo(const RemoteDvrInfo &model) : DvrInfo(model), - host(model.host), - port(model.port) -{ -} - -RemoteDvrInfo::~RemoteDvrInfo() -{ -} - -RemoteDvrInfo *RemoteDvrInfo::clone() const -{ - return new RemoteDvrInfo(*this); -} - -/* - Old: - void RemoteDvrInfo::extractRemoteId(const std::string &name, std::string &o_host, int &o_port, std::string &o_dev) const - { - char dev[MAXHYDROGENDEVICE] = {0}; - char host[MAXSBUF] = {0}; - - int hydrogen_port = HYDROGENPORT; - if (sscanf(name.c_str(), "%[^@]@%[^:]:%d", dev, host, &hydrogen_port) < 2) - { - // Device missing? Try a different syntax for all devices - if (sscanf(name.c_str(), "@%[^:]:%d", host, &hydrogen_port) < 1) - { - log(fmt::format("Bad remote device syntax: %s\n", name.c_str())); - // Bye(); - } - } - - o_host = host; - o_port = hydrogen_port; - o_dev = dev; - } -*/ - -void RemoteDvrInfo::extractRemoteId(const std::string &name, std::string &o_host, int &o_port, std::string &o_dev) const -{ - std::string dev; - std::string host; - - // Extract host and port from name - size_t atPos = name.find('@'); - size_t colonPos = name.find(':'); - size_t portEndPos = name.size(); - - if (atPos != std::string::npos && colonPos != std::string::npos && colonPos > atPos + 1) - { - dev = name.substr(0, atPos); - host = name.substr(atPos + 1, colonPos - atPos - 1); - o_port = std::stoi(name.substr(colonPos + 1, portEndPos - colonPos - 1)); - } - else if (atPos == std::string::npos && colonPos != std::string::npos && colonPos > 0) - { - host = name.substr(0, colonPos); - o_port = std::stoi(name.substr(colonPos + 1, portEndPos - colonPos - 1)); - } - - o_host = host; - o_dev = dev; -} - -/* start the given remote HYDROGEN driver connection. - * exit if trouble. - */ -void RemoteDvrInfo::start() -{ - int sockfd; - std::string dev; - extractRemoteId(name, host, port, dev); - - /* connect */ - sockfd = openHYDROGENServer(); - - /* record flag pid, io channels, init lp and snoop list */ - - this->setFds(sockfd, sockfd); - - if (verbose > 0) - DLOG_F(INFO, "socket={}", sockfd); - - /* N.B. storing name now is key to limiting outbound traffic to this - * dev. - */ - if (!dev.empty()) - this->dev.insert(dev); - - /* Sending getProperties with device lets remote server limit its - * outbound (and our inbound) traffic on this socket to this device. - */ - XMLEle *root = addXMLEle(NULL, "getProperties"); - - if (!dev.empty()) - { - addXMLAtt(root, "device", dev.c_str()); - addXMLAtt(root, "version", TO_STRING(HYDROGENV)); - } - else - { - // This informs downstream server that it is connecting to an upstream server - // and not a regular client. The difference is in how it treats snooping properties - // among properties. - addXMLAtt(root, "device", "*"); - addXMLAtt(root, "version", TO_STRING(HYDROGENV)); - } - - Msg *mp = new Msg(nullptr, root); - - // pushmsg can kill this. do at end - pushMsg(mp); -} - -int RemoteDvrInfo::openHYDROGENServer() -{ - struct sockaddr_in serv_addr; - struct hostent *hp; - int sockfd; - - /* lookup host address */ - hp = gethostbyname(host.c_str()); - if (!hp) - { - LOG_F(ERROR, "gethostbyname({}): {}", host.c_str(), strerror(errno)); - // Bye(); - } - - /* create a socket to the HYDROGEN server */ - (void)memset((char *)&serv_addr, 0, sizeof(serv_addr)); - serv_addr.sin_family = AF_INET; - serv_addr.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr_list[0]))->s_addr; - serv_addr.sin_port = htons(port); - if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) - { - LOG_F(ERROR, "socket({},{}): {}", host.c_str(), port, strerror(errno)); - // Bye(); - } - - /* connect */ - if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) - { - LOG_F(ERROR, "connect({},{}): {}", host.c_str(), port, strerror(errno)); - // Bye(); - } - - /* ok */ - return (sockfd); -} \ No newline at end of file diff --git a/modules/hydrogen_loader/remote_driver.hpp b/modules/hydrogen_loader/remote_driver.hpp deleted file mode 100644 index 544d5535..00000000 --- a/modules/hydrogen_loader/remote_driver.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "driver_info.hpp" - -class RemoteDvrInfo : public DvrInfo -{ - /* open a connection to the given host and port or die. - * return socket fd. - */ - int openHYDROGENServer(); - - void extractRemoteId(const std::string &name, std::string &o_host, int &o_port, std::string &o_dev) const; - -protected: - RemoteDvrInfo(const RemoteDvrInfo &model); - -public: - std::string host; - int port; - - RemoteDvrInfo(); - virtual ~RemoteDvrInfo(); - - virtual void start(); - - virtual RemoteDvrInfo *clone() const; - - virtual const std::string remoteServerUid() const - { - return std::string(host) + ":" + std::to_string(port); - } -}; \ No newline at end of file diff --git a/modules/hydrogen_loader/serialize.cpp b/modules/hydrogen_loader/serialize.cpp deleted file mode 100644 index cc82d43c..00000000 --- a/modules/hydrogen_loader/serialize.cpp +++ /dev/null @@ -1,583 +0,0 @@ -#include "serialize.hpp" - -#include -#if ENABLE_FASTHASH -#include "emhash/hash_table8.hpp" -#else -#include -#endif - -#include - -#include "base64.hpp" - -#ifdef _WIN32 -#include -#endif - -#include "io.hpp" -#include "message.hpp" -#include "message_queue.hpp" -#include "xml_util.hpp" -#include "hydrogen_server.hpp" - -#include "atom/log/loguru.hpp" - -SerializedMsg::SerializedMsg(Msg *parent) : asyncProgress(), owner(parent), awaiters(), chuncks(), ownBuffers() -{ - blockedProducer = nullptr; - // At first, everything is required. - for (auto fd : parent->sharedBuffers) - { - if (fd != -1) - { - requirements.sharedBuffers.insert(fd); - } - } - requirements.xml = true; - asyncStatus = PENDING; -#ifdef USE_LIBUV - uv_async_init(loop, &asyncProgress, (uv_async_cb)&SerializedMsg::async_progressed); - asyncProgress.data = this; -#else - asyncProgress.set(this); -#endif -} - -// Delete occurs when no async task is running and no awaiters are left -SerializedMsg::~SerializedMsg() -{ - for (auto buff : ownBuffers) - { - free(buff); - } -} - -bool SerializedMsg::async_canceled() -{ - std::lock_guard guard(lock); - return asyncStatus == CANCELING; -} - -void SerializedMsg::async_updateRequirement(const SerializationRequirement &req) -{ - std::lock_guard guard(lock); - if (this->requirements == req) - { - return; - } - this->requirements = req; -#ifdef USE_LIBUV - uv_async_send(&asyncProgress); -#else - asyncProgress.send(); -#endif -} - -void SerializedMsg::async_pushChunck(const MsgChunck &m) -{ - std::lock_guard guard(lock); - - this->chuncks.push_back(m); -#ifdef USE_LIBUV - uv_async_send(&asyncProgress); -#else - asyncProgress.send(); -#endif -} - -void SerializedMsg::async_done() -{ - std::lock_guard guard(lock); - asyncStatus = TERMINATED; -#ifdef USE_LIBUV - uv_async_send(&asyncProgress); -#else - asyncProgress.send(); -#endif -} - -void SerializedMsg::async_start() -{ - std::lock_guard guard(lock); - if (asyncStatus != PENDING) - { - return; - } - - asyncStatus = RUNNING; - if (generateContentAsync()) - { -#ifdef USE_LIBUV - uv_async_send(&asyncProgress); -#else - asyncProgress.start(); -#endif - - std::thread t([this]() - { generateContent(); }); - t.detach(); - } - else - { - generateContent(); - } -} - -void SerializedMsg::async_progressed() -{ - std::lock_guard guard(lock); - - if (asyncStatus == TERMINATED) - { -// FIXME: unblock ? -#ifdef USE_LIBUV - uv_close((uv_handle_t *)&this->asyncProgress, NULL); -#else - asyncProgress.stop(); -#endif - } - - // Update ios of awaiters - for (auto awaiter : awaiters) - { - awaiter->messageMayHaveProgressed(this); - } - - // Then prune - owner->prune(); -} - -bool SerializedMsg::isAsyncRunning() -{ - std::lock_guard guard(lock); - - return (asyncStatus == RUNNING) || (asyncStatus == CANCELING); -} - -bool SerializedMsg::requestContent(const MsgChunckIterator &position) -{ - std::lock_guard guard(lock); - - if (asyncStatus == PENDING) - { - async_start(); - } - - if (asyncStatus == TERMINATED) - { - return true; - } - - // Not reached the last chunck - if (position.chunckId < chuncks.size()) - { - return true; - } - - return false; -} - -bool SerializedMsg::getContent(MsgChunckIterator &from, void *&data, ssize_t &size, - std::vector> &sharedBuffers) -{ - std::lock_guard guard(lock); - - if (asyncStatus != TERMINATED && from.chunckId >= chuncks.size()) - { - // Not ready yet - return false; - } - - if (from.chunckId == chuncks.size()) - { - // Done - data = 0; - size = 0; - from.endReached = true; - return true; - } - - const MsgChunck &ck = chuncks[from.chunckId]; - - if (from.chunckOffset == 0) - { - sharedBuffers = ck.sharedBufferIdsToAttach; - } - else - { - sharedBuffers.clear(); - } - - data = ck.content + from.chunckOffset; - size = ck.contentLength - from.chunckOffset; - return true; -} - -void SerializedMsg::advance(MsgChunckIterator &iter, ssize_t s) -{ - std::lock_guard guard(lock); - - MsgChunck &cur = chuncks[iter.chunckId]; - iter.chunckOffset += s; - if (iter.chunckOffset >= cur.contentLength) - { - iter.chunckId++; - iter.chunckOffset = 0; - if (iter.chunckId >= chuncks.size() && asyncStatus == TERMINATED) - { - iter.endReached = true; - } - } -} - -void SerializedMsg::addAwaiter(MsgQueue *q) -{ - awaiters.insert(q); -} - -void SerializedMsg::release(MsgQueue *q) -{ - awaiters.erase(q); - if (awaiters.empty() && !isAsyncRunning()) - { - owner->releaseSerialization(this); - } -} - -void SerializedMsg::collectRequirements(SerializationRequirement &sr) -{ - sr.add(requirements); -} - -// This is called when a received message require additional // work, to avoid overflow -void SerializedMsg::blockReceiver(MsgQueue *receiver) -{ - // TODO : implement or remove - (void)receiver; -} - -ssize_t SerializedMsg::queueSize() -{ - return owner->queueSize; -} - -SerializedMsgWithoutSharedBuffer::SerializedMsgWithoutSharedBuffer(Msg *parent) : SerializedMsg(parent) -{ -} - -SerializedMsgWithoutSharedBuffer::~SerializedMsgWithoutSharedBuffer() -{ -} - -SerializedMsgWithSharedBuffer::SerializedMsgWithSharedBuffer(Msg *parent) : SerializedMsg(parent), ownSharedBuffers() -{ -} - -SerializedMsgWithSharedBuffer::~SerializedMsgWithSharedBuffer() -{ - for (auto id : ownSharedBuffers) - { - close(id); - } -} - -bool SerializedMsgWithSharedBuffer::detectInlineBlobs() -{ - for (auto blobContent : findBlobElements(owner->xmlContent)) - { - // C'est pas trivial, dans ce cas, car il faut les réattacher - std::string attached = findXMLAttValu(blobContent, "attached"); - if (attached != "true") - { - return true; - } - } - return false; -} - -bool SerializedMsgWithoutSharedBuffer::generateContentAsync() const -{ - return owner->hasInlineBlobs || owner->hasSharedBufferBlobs; -} - -void SerializedMsgWithoutSharedBuffer::generateContent() -{ - // Convert every shared buffer into an inline base64 - auto xmlContent = owner->xmlContent; - - std::vector cdata; - // Every cdata will have either sharedBuffer or sharedCData - std::vector sharedBuffers; - std::vector xmlSizes; - std::vector sharedCData; - - std::unordered_map replacement; - - int ownerSharedBufferId = 0; - - // Identify shared buffer blob to base64 them - // Identify base64 blob to avoid copying them (we'll copy the cdata) - for (auto blobContent : findBlobElements(xmlContent)) - { - std::string attached = findXMLAttValu(blobContent, "attached"); - - if (attached != "true" && pcdatalenXMLEle(blobContent) == 0) - { - continue; - } - - XMLEle *clone = shallowCloneXMLEle(blobContent); - rmXMLAtt(clone, "attached"); - editXMLEle(clone, "_"); - - replacement[blobContent] = clone; - cdata.push_back(clone); - - if (attached == "true") - { - rmXMLAtt(clone, "enclen"); - - // Get the size if present - ssize_t size = -1; - parseBlobSize(clone, size); - - // FIXME: we could add enclen there - - // Put something here for later replacement - sharedBuffers.push_back(owner->sharedBuffers[ownerSharedBufferId++]); - xmlSizes.push_back(size); - sharedCData.push_back(nullptr); - } - else - { - sharedBuffers.push_back(-1); - xmlSizes.push_back(-1); - sharedCData.push_back(blobContent); - } - } - - if (replacement.empty()) - { - // Just print the content as is... - - char *model = (char *)malloc(sprlXMLEle(xmlContent, 0) + 1); - int modelSize = sprXMLEle(model, xmlContent, 0); - - ownBuffers.push_back(model); - - async_pushChunck(MsgChunck(model, modelSize)); - - // FIXME: lower requirements asap... how to do that ? - // requirements.xml = false; - // requirements.sharedBuffers.clear(); - } - else - { - // Create a replacement that shares original CData buffers - xmlContent = cloneXMLEleWithReplacementMap(xmlContent, replacement); - - std::vector modelCdataOffset(cdata.size()); - - char *model = (char *)malloc(sprlXMLEle(xmlContent, 0) + 1); - int modelSize = sprXMLEle(model, xmlContent, 0); - - ownBuffers.push_back(model); - - // Get the element offset - for (std::size_t i = 0; i < cdata.size(); ++i) - { - modelCdataOffset[i] = sprXMLCDataOffset(xmlContent, cdata[i], 0); - } - delXMLEle(xmlContent); - - std::vector fds(cdata.size()); - std::vector blobs(cdata.size()); - std::vector sizes(cdata.size()); - std::vector attachedSizes(cdata.size()); - - // Attach all blobs - for (std::size_t i = 0; i < cdata.size(); ++i) - { - if (sharedBuffers[i] != -1) - { - fds[i] = sharedBuffers[i]; - - size_t dataSize; -#ifdef _WIN32 - blobs[i] = attachSharedBuffer((HANDLE)_get_osfhandle(fds[i]), dataSize); -#else - blobs[i] = attachSharedBuffer(fds[i], dataSize); -#endif - attachedSizes[i] = dataSize; - - // check dataSize is compatible with the blob element's size - // It's mandatory for attached blob to give their size - if (xmlSizes[i] != -1 && ((size_t)xmlSizes[i]) <= dataSize) - { - dataSize = xmlSizes[i]; - } - sizes[i] = dataSize; - } - else - { - fds[i] = -1; - } - } - - // Copy from model or blob (streaming base64 encode) - int modelOffset = 0; - for (std::size_t i = 0; i < cdata.size(); ++i) - { - int cdataOffset = modelCdataOffset[i]; - if (cdataOffset > modelOffset) - { - async_pushChunck(MsgChunck(model + modelOffset, cdataOffset - modelOffset)); - } - // Skip the dummy cdata completly - modelOffset = cdataOffset + 1; - - // Perform inplace base64 - // FIXME: could be streamed/splitted - - if (fds[i] != -1) - { - // Add a binary chunck. This needs base64 convertion - // FIXME: the size here should be the size of the blob element - unsigned long buffSze = sizes[i]; - const unsigned char *src = (const unsigned char *)blobs[i]; - - // split here in smaller chuncks for faster startup - // This allow starting write before the whole blob is converted - while (buffSze > 0) - { - // We need a block size multiple of 24 bits (3 bytes) - unsigned long sze = buffSze > 3 * 16384 ? 3 * 16384 : buffSze; - - char *buffer = (char *)malloc(4 * sze / 3 + 4); - ownBuffers.push_back(buffer); - int base64Count = to64frombits_s((unsigned char *)buffer, src, sze, (4 * sze / 3 + 4)); - - async_pushChunck(MsgChunck(buffer, base64Count)); - - buffSze -= sze; - src += sze; - } - - // Dettach blobs ASAP -#ifdef _WIN32 - detachSharedBuffer(blobs[i]); -#else - detachSharedBuffer(fds[i], blobs[i], attachedSizes[i]); -#endif - // requirements.sharedBuffers.erase(fds[i]); - } - else - { - // Add an already ready cdata section - - auto len = pcdatalenXMLEle(sharedCData[i]); - auto data = pcdataXMLEle(sharedCData[i]); - async_pushChunck(MsgChunck(data, len)); - } - } - - if (modelOffset < modelSize) - { - async_pushChunck(MsgChunck(model + modelOffset, modelSize - modelOffset)); - modelOffset = modelSize; - } - } - async_done(); -} - -bool SerializedMsgWithSharedBuffer::generateContentAsync() const -{ - return owner->hasInlineBlobs; -} - -void SerializedMsgWithSharedBuffer::generateContent() -{ - // Convert every inline base64 blob from xml into an attached buffer - auto xmlContent = owner->xmlContent; - - std::vector sharedBuffers = owner->sharedBuffers; - - std::unordered_map replacement; - int blobPos = 0; - for (auto blobContent : findBlobElements(owner->xmlContent)) - { - if (!pcdatalenXMLEle(blobContent)) - { - continue; - } - std::string attached = findXMLAttValu(blobContent, "attached"); - if (attached != "true") - { - // We need to replace. - XMLEle *clone = shallowCloneXMLEle(blobContent); - rmXMLAtt(clone, "enclen"); - rmXMLAtt(clone, "attached"); - addXMLAtt(clone, "attached", "true"); - - replacement[blobContent] = clone; - - int base64datalen = pcdatalenXMLEle(blobContent); - char *base64data = pcdataXMLEle(blobContent); - // Shall we really trust the size here ? - - ssize_t size; - if (!parseBlobSize(blobContent, size)) - { - DLOG_F(WARNING, "Missing size value for blob"); - size = 1; - } - - void *blob = IDSharedBlobAlloc(size); - if (blob == nullptr) - { - LOG_F(ERROR, "Unable to allocate shared buffer of size %ld : %s\n", size, strerror(errno)); - ::exit(1); - } - DLOG_F(INFO, "Blob allocated at %p\n", blob); - - int actualLen = from64tobits_fast((char *)blob, base64data, base64datalen); - - if (actualLen != size) - { - // FIXME: WTF ? at least prevent overflow ??? - DLOG_F(INFO, "Blob size mismatch after base64dec: %lld vs %lld\n", (long long int)actualLen, (long long int)size); - } - - int newFd = IDSharedBlobGetFd(blob); - ownSharedBuffers.insert(newFd); - - IDSharedBlobDettach(blob); - - sharedBuffers.insert(sharedBuffers.begin() + blobPos, newFd); - } - blobPos++; - } - - if (!replacement.empty()) - { - // Work on a copy --- but we don't want to copy the blob !!! - xmlContent = cloneXMLEleWithReplacementMap(xmlContent, replacement); - } - - // Now create a Chunk from xmlContent - MsgChunck chunck; - - chunck.content = (char *)malloc(sprlXMLEle(xmlContent, 0) + 1); - ownBuffers.push_back(chunck.content); - chunck.contentLength = sprXMLEle(chunck.content, xmlContent, 0); - chunck.sharedBufferIdsToAttach = sharedBuffers; - - async_pushChunck(chunck); - - if (!replacement.empty()) - { - delXMLEle(xmlContent); - } - async_done(); -} diff --git a/modules/hydrogen_loader/serialize.hpp b/modules/hydrogen_loader/serialize.hpp deleted file mode 100644 index 67231fe2..00000000 --- a/modules/hydrogen_loader/serialize.hpp +++ /dev/null @@ -1,167 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#ifdef USE_LIBUV -#include -#else -#include -#endif - -class Msg; -class MsgQueue; -class MsgChunck; -class MsgChunckIterator; - -class SerializationRequirement -{ - friend class Msg; - friend class SerializedMsg; - - // If the xml is still required - bool xml; - // Set of sharedBuffer that are still required - std::set sharedBuffers; - - SerializationRequirement() : sharedBuffers() - { - xml = false; - } - - void add(const SerializationRequirement &from) - { - xml |= from.xml; - for (auto fd : from.sharedBuffers) - { - sharedBuffers.insert(fd); - } - } - - bool operator==(const SerializationRequirement &sr) const - { - return (xml == sr.xml) && (sharedBuffers == sr.sharedBuffers); - } -}; - -enum SerializationStatus -{ - PENDING, - RUNNING, - CANCELING, - TERMINATED -}; - -class SerializedMsg -{ - friend class Msg; - friend class MsgChunckIterator; - - std::recursive_mutex lock; -#ifdef USE_LIBUV - uv_async_t asyncProgress; -#else - ev::async asyncProgress; -#endif - - // Start a thread for execution of asyncRun - void async_start(); - void async_cancel(); - - // Called within main loop when async task did some progress - void async_progressed(); - - // The requirements. Prior to starting, everything is required. - SerializationRequirement requirements; - - void produce(bool sync); - -protected: - // These methods are to be called from asyncRun - bool async_canceled(); - void async_updateRequirement(const SerializationRequirement &n); - void async_pushChunck(const MsgChunck &m); - void async_done(); - - // True if a producing thread is active - bool isAsyncRunning(); - -protected: - SerializationStatus asyncStatus; - Msg *owner; - - MsgQueue *blockedProducer; - - std::set awaiters; - -private: - std::vector chuncks; - -protected: - // Buffers malloced during asyncRun - std::list ownBuffers; - - // This will notify awaiters and possibly release the owner - void onDataReady(); - - virtual bool generateContentAsync() const = 0; - virtual void generateContent() = 0; - - void collectRequirements(SerializationRequirement &req); - - // The task will cancel itself if all owner release it - void abort(); - - // Make sure the given receiver will not be processed until this task complete - // TODO : to implement + make sure the task start when it actually block something - void blockReceiver(MsgQueue *toblock); - -public: - SerializedMsg(Msg *parent); - virtual ~SerializedMsg(); - - // Calling requestContent will start production - // Return true if some content is available - bool requestContent(const MsgChunckIterator &position); - - // Return true if some content is available - // It is possible to have 0 to send, meaning end was actually reached - bool getContent(MsgChunckIterator &position, void *&data, ssize_t &nsend, std::vector &sharedBuffers); - - void advance(MsgChunckIterator &position, ssize_t s); - - // When a queue is done with sending this message - void release(MsgQueue *from); - - void addAwaiter(MsgQueue *awaiter); - - ssize_t queueSize(); -}; - -class SerializedMsgWithSharedBuffer : public SerializedMsg -{ - std::set ownSharedBuffers; - -protected: - bool detectInlineBlobs(); - -public: - SerializedMsgWithSharedBuffer(Msg *parent); - virtual ~SerializedMsgWithSharedBuffer(); - - virtual bool generateContentAsync() const; - virtual void generateContent(); -}; - -class SerializedMsgWithoutSharedBuffer : public SerializedMsg -{ - -public: - SerializedMsgWithoutSharedBuffer(Msg *parent); - virtual ~SerializedMsgWithoutSharedBuffer(); - - virtual bool generateContentAsync() const; - virtual void generateContent(); -}; \ No newline at end of file diff --git a/modules/hydrogen_loader/signal.cpp b/modules/hydrogen_loader/signal.cpp deleted file mode 100644 index d249c59e..00000000 --- a/modules/hydrogen_loader/signal.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "signal.hpp" -#include - -std::map SignalHandler::handlers; - -void SignalHandler::handleSignal(int signal) -{ - auto it = handlers.find(signal); - if (it != handlers.end()) - { - it->second(); - } -} - -void SignalHandler::registerHandler(int signal, const SignalHandlerFunc &handlerFunc) -{ -#ifdef _WIN32 - if (signal == SIGINT || signal == SIGTERM) - { - SetConsoleCtrlHandler(handleConsoleEvent, TRUE); - } - else - { - handlers[signal] = handlerFunc; - } -#else - handlers[signal] = handlerFunc; - std::signal(signal, handleSignal); -#endif -} - -void SignalHandler::unregisterHandler(int signal) -{ -#ifdef _WIN32 - if (signal == SIGINT || signal == SIGTERM) - { - SetConsoleCtrlHandler(handleConsoleEvent, FALSE); - } - else - { - handlers.erase(signal); - } -#else - handlers.erase(signal); - std::signal(signal, SIG_DFL); -#endif -} - -#ifdef _WIN32 -BOOL WINAPI SignalHandler::handleConsoleEvent(DWORD eventType) -{ - switch (eventType) - { - case CTRL_C_EVENT: - case CTRL_BREAK_EVENT: - case CTRL_CLOSE_EVENT: - case CTRL_SHUTDOWN_EVENT: - handleSignal(SIGINT); - return TRUE; - default: - return FALSE; - } -} -#endif \ No newline at end of file diff --git a/modules/hydrogen_loader/signal.hpp b/modules/hydrogen_loader/signal.hpp deleted file mode 100644 index f2cbd4dc..00000000 --- a/modules/hydrogen_loader/signal.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef SIGNALHANDLER_H -#define SIGNALHANDLER_H - -#include -#include - -#ifdef _WIN32 -#include -#endif - -class SignalHandler -{ -public: - using SignalHandlerFunc = std::function; - - static void handleSignal(int signal); - static void registerHandler(int signal, const SignalHandlerFunc &handlerFunc); - static void unregisterHandler(int signal); - -private: - static std::map handlers; - -#ifdef _WIN32 - static BOOL WINAPI handleConsoleEvent(DWORD eventType); -#endif -}; - -#endif // SIGNALHANDLER_H diff --git a/modules/hydrogen_loader/tcp_server.cpp b/modules/hydrogen_loader/tcp_server.cpp deleted file mode 100644 index 5124adac..00000000 --- a/modules/hydrogen_loader/tcp_server.cpp +++ /dev/null @@ -1,249 +0,0 @@ -#include "tcp_server.hpp" - -#ifdef _WIN32 -#include -#include -#else -#include -#endif - -#include -#include - -#include "io.hpp" -#include "client_info.hpp" -#include "hydrogen_server.hpp" - -#include "atom/log/loguru.hpp" - -TcpServer::TcpServer(int port) : port(port) -{ -#ifdef USE_LIBUV - poll_watcher.data = this; -#else - sfdev.set(this); -#endif -} - -#ifdef USE_LIBUV -void TcpServer::ioCb(uv_poll_t *watcher, int status, int revents) -{ - if (status < 0) - { - LOG_F(ERROR, "Error on tcp server socket: {}", uv_strerror(status)); - // Bye(); - } - else if (revents & UV_READABLE) - { - accept(); - } -} - -void TcpServer::listen() -{ - struct sockaddr_in serv_socket; - int reuse = 1; - - /* make socket endpoint */ - if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) - { - LOG_F(ERROR, "socket: %s\n", strerror(errno)); - // Bye(); - } - - /* bind to given port for any IP address */ - memset(&serv_socket, 0, sizeof(serv_socket)); - serv_socket.sin_family = AF_INET; -#ifdef SSH_TUNNEL - serv_socket.sin_addr.s_addr = htonl(INADDR_LOOPBACK); -#else - serv_socket.sin_addr.s_addr = htonl(INADDR_ANY); -#endif - serv_socket.sin_port = htons((unsigned short)port); -#ifdef _WIN32 - if (setsockopt(sfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&reuse, sizeof(reuse)) < 0) - { - LOG_F(ERROR, "Failed to set receive timeout."); - close(sfd); - sfd = -1; - return; - } -#else - if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) - { - LOG_F(ERROR, "setsockopt: %s\n", strerror(errno)); - // Bye(); - } -#endif - if (bind(sfd, (struct sockaddr *)&serv_socket, sizeof(serv_socket)) < 0) - { - LOG_F(ERROR, "bind: %s\n", strerror(errno)); - // Bye(); - } - - /* willing to accept connections with a backlog of 5 pending */ - if (::listen(sfd, 5) < 0) - { - LOG_F(ERROR, "listen: %s\n", strerror(errno)); - // Bye(); - } - -#ifdef _WIN32 - int fd = _fileno(reinterpret_cast(_get_osfhandle(sfd))); - _setmode(fd, _O_BINARY); -#else - fcntl(sfd, F_SETFL, fcntl(sfd, F_GETFL, 0) | O_NONBLOCK); -#endif - - // 创建uv_poll_t对象,并注册READABLE事件回调函数 - uv_poll_init_socket(loop, &poll_watcher, sfd); - uv_poll_start(&poll_watcher, UV_READABLE, (uv_poll_cb)&TcpServer::ioCb); - - /* 开始事件循环 */ - uv_run(loop, UV_RUN_DEFAULT); - - /* ok */ - if (verbose > 0) - DLOG_F(INFO, "listening to port %d on fd %d\n", port, sfd); -} - - -void TcpServer::accept() -{ - struct sockaddr_in cli_socket; - socklen_t cli_len; - int cli_fd; - - /* get a private connection to new client */ - cli_len = sizeof(cli_socket); - cli_fd = ::accept(sfd, (struct sockaddr *)&cli_socket, &cli_len); - if (cli_fd < 0) - { - if (errno == EAGAIN || errno == EWOULDBLOCK) - return; - - LOG_F(ERROR, "accept: %s\n", strerror(errno)); - // Bye(); - } - - ClInfo *cp = new ClInfo(false); - - /* rig up new clinfo entry */ - cp->setFds(cli_fd, cli_fd); - -#ifdef OSX_EMBEDED_MODE - fprintf(stderr, "CLIENTS %d\n", clients.size()); - fflush(stderr); -#endif -} - -#else - -void TcpServer::ioCb(ev::io &, int revents) -{ - if (revents & EV_ERROR) - { - int sockErrno = readFdError(this->sfd); - if (sockErrno) - { - LOG_F(ERROR, "Error on tcp server socket: %s\n", strerror(sockErrno)); - // Bye(); - } - } - if (revents & EV_READ) - { - accept(); - } -} - -void TcpServer::listen() -{ - struct sockaddr_in serv_socket; - int reuse = 1; - - /* make socket endpoint */ - if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) - { - LOG_F(ERROR, "socket: %s\n", strerror(errno)); - // Bye(); - } - - /* bind to given port for any IP address */ - memset(&serv_socket, 0, sizeof(serv_socket)); - serv_socket.sin_family = AF_INET; -#ifdef SSH_TUNNEL - serv_socket.sin_addr.s_addr = htonl(INADDR_LOOPBACK); -#else - serv_socket.sin_addr.s_addr = htonl(INADDR_ANY); -#endif - serv_socket.sin_port = htons((unsigned short)port); -#ifdef _WIN32 - if (setsockopt(sfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&reuse, sizeof(reuse)) < 0) - { - LOG_F(ERROR, "Failed to set receive timeout."); - close(sfd); - sfd = -1; - return; - } -#else - if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) - { - LOG_F(ERROR, "setsockopt: %s\n", strerror(errno)); - // Bye(); - } -#endif - if (bind(sfd, (struct sockaddr *)&serv_socket, sizeof(serv_socket)) < 0) - { - LOG_F(ERROR, "bind: %s\n", strerror(errno)); - // Bye(); - } - - /* willing to accept connections with a backlog of 5 pending */ - if (::listen(sfd, 5) < 0) - { - LOG_F(ERROR, "listen: %s\n", strerror(errno)); - // Bye(); - } - -#ifdef _WIN32 - int fd = _fileno(reinterpret_cast(_get_osfhandle(sfd))); - _setmode(fd, _O_BINARY); -#else - fcntl(sfd, F_SETFL, fcntl(sfd, F_GETFL, 0) | O_NONBLOCK); -#endif - sfdev.start(sfd, EV_READ); - - /* ok */ - if (verbose > 0) - DLOG_F(INFO, "listening to port %d on fd %d\n", port, sfd); -} - -void TcpServer::accept() -{ - struct sockaddr_in cli_socket; - socklen_t cli_len; - int cli_fd; - - /* get a private connection to new client */ - cli_len = sizeof(cli_socket); - cli_fd = ::accept(sfd, (struct sockaddr *)&cli_socket, &cli_len); - if (cli_fd < 0) - { - if (errno == EAGAIN || errno == EWOULDBLOCK) - return; - - LOG_F(ERROR, "accept: %s\n", strerror(errno)); - // Bye(); - } - - ClInfo *cp = new ClInfo(false); - - /* rig up new clinfo entry */ - cp->setFds(cli_fd, cli_fd); - -#ifdef OSX_EMBEDED_MODE - fprintf(stderr, "CLIENTS %d\n", clients.size()); - fflush(stderr); -#endif -} -#endif diff --git a/modules/hydrogen_loader/tcp_server.hpp b/modules/hydrogen_loader/tcp_server.hpp deleted file mode 100644 index 6f672ccf..00000000 --- a/modules/hydrogen_loader/tcp_server.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#ifdef _WIN32 - -#else -#include -#endif - -#ifdef USE_LIBUV -#include -#else -#include -#endif - -class TcpServer -{ - int port; - int sfd = -1; -#ifdef USE_LIBUV - uv_poll_t poll_watcher; -#else - ev::io sfdev; -#endif - - -#ifdef USE_LIBUV - void ioCb(uv_poll_t *watcher, int status, int revents); - void accept(); -#else - void ioCb(ev::io &watcher, int revents); - /* prepare for new client arriving on socket. - * exit if trouble. - */ - void accept(); -#endif - -public: - TcpServer(int port); - - /* create the public HYDROGEN Driver endpoint lsocket on port. - * return server socket else exit. - */ - void listen(); -}; diff --git a/modules/hydrogen_loader/time.cpp b/modules/hydrogen_loader/time.cpp deleted file mode 100644 index 431900bf..00000000 --- a/modules/hydrogen_loader/time.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "time.hpp" - -#include - -/* fill s with current UT string. - * if no s, use a static buffer - * return s or buffer. - * N.B. if use our buffer, be sure to use before calling again - */ -char *hydrogen_tstamp(char *s) -{ - static char sbuf[64]; - struct tm *tp; - time_t t; - - time(&t); - tp = gmtime(&t); - if (!s) - s = sbuf; - strftime(s, sizeof(sbuf), "%Y-%m-%dT%H:%M:%S", tp); - return (s); -} \ No newline at end of file diff --git a/modules/hydrogen_loader/time.hpp b/modules/hydrogen_loader/time.hpp deleted file mode 100644 index b8501b40..00000000 --- a/modules/hydrogen_loader/time.hpp +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -char *hydrogen_tstamp(char *s); \ No newline at end of file diff --git a/modules/hydrogen_loader/unix_server.cpp b/modules/hydrogen_loader/unix_server.cpp deleted file mode 100644 index 5ee6ac7f..00000000 --- a/modules/hydrogen_loader/unix_server.cpp +++ /dev/null @@ -1,149 +0,0 @@ - -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - -UnixServer::UnixServer(const std::string &path) : path(path) -{ - sfdev.set(this); -} - -void UnixServer::log(const std::string &str) const -{ - std::string logLine = "Local server: "; - logLine += str; - ::log(logLine); -} - -void UnixServer::ioCb(ev::io &, int revents) -{ - if (revents & EV_ERROR) - { - int sockErrno = readFdError(this->sfd); - if (sockErrno) - { - log(fmt("Error on unix socket: %s\n", strerror(sockErrno))); - // Bye(); - } - } - if (revents & EV_READ) - { - accept(); - } -} - -static void initUnixSocketAddr(const std::string &unixAddr, struct sockaddr_un &serv_addr_un, socklen_t &addrlen, bool bind) -{ - memset(&serv_addr_un, 0, sizeof(serv_addr_un)); - serv_addr_un.sun_family = AF_UNIX; - -#ifdef __linux__ - (void)bind; - - // Using abstract socket path to avoid filesystem boilerplate - strncpy(serv_addr_un.sun_path + 1, unixAddr.c_str(), sizeof(serv_addr_un.sun_path) - 1); - - int len = offsetof(struct sockaddr_un, sun_path) + unixAddr.size() + 1; - - addrlen = len; -#else - // Using filesystem socket path - strncpy(serv_addr_un.sun_path, unixAddr.c_str(), sizeof(serv_addr_un.sun_path) - 1); - - int len = offsetof(struct sockaddr_un, sun_path) + unixAddr.size(); - - if (bind) - { - unlink(unixAddr.c_str()); - } -#endif - addrlen = len; -} - -void UnixServer::listen() -{ - struct sockaddr_un serv_socket; - - /* make socket endpoint */ - if ((sfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) - { - log(fmt("socket: %s\n", strerror(errno))); - // Bye(); - } - - int reuse = 1; - if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) - { - log(fmt("setsockopt: %s\n", strerror(errno))); - // Bye(); - } - - /* bind to given path as unix address */ - socklen_t len; - initUnixSocketAddr(path, serv_socket, len, true); - - if (bind(sfd, (struct sockaddr *)&serv_socket, len) < 0) - { - log(fmt("bind: %s\n", strerror(errno))); - // Bye(); - } - - /* willing to accept connections with a backlog of 5 pending */ - if (::listen(sfd, 5) < 0) - { - log(fmt("listen: %s\n", strerror(errno))); - // Bye(); - } - - fcntl(sfd, F_SETFL, fcntl(sfd, F_GETFL, 0) | O_NONBLOCK); - sfdev.start(sfd, EV_READ); - - /* ok */ - if (verbose > 0) - log(fmt("listening on local domain at: @%s\n", path.c_str())); -} - -void UnixServer::accept() -{ - int cli_fd; - - /* get a private connection to new client */ - cli_fd = ::accept(sfd, 0, 0); - if (cli_fd < 0) - { - if (errno == EAGAIN || errno == EWOULDBLOCK) - return; - - log(fmt("accept: %s\n", strerror(errno))); - // Bye(); - } - - ClInfo *cp = new ClInfo(true); - - /* rig up new clinfo entry */ - cp->setFds(cli_fd, cli_fd); - - if (verbose > 0) - { -#ifdef SO_PEERCRED - struct ucred ucred; - - socklen_t len = sizeof(struct ucred); - if (getsockopt(cli_fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) - { - log(fmt("getsockopt failed: %s\n", strerror(errno))); - // Bye(); - } - - cp->log(fmt("new arrival from local pid %ld (user: %ld:%ld) - welcome!\n", (long)ucred.pid, (long)ucred.uid, - (long)ucred.gid)); -#else - cp->log(fmt("new arrival from local domain - welcome!\n")); -#endif - } - -#ifdef OSX_EMBEDED_MODE - fprintf(stderr, "CLIENTS %d\n", clients.size()); - fflush(stderr); -#endif -} - -#endif // ENABLE_HYDROGEN_SHARED_MEMORY \ No newline at end of file diff --git a/modules/hydrogen_loader/unix_server.hpp b/modules/hydrogen_loader/unix_server.hpp deleted file mode 100644 index 0b89e1ef..00000000 --- a/modules/hydrogen_loader/unix_server.hpp +++ /dev/null @@ -1,28 +0,0 @@ - -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - -class UnixServer -{ - std::string path; - int sfd = -1; - ev::io sfdev; - - void accept(); - void ioCb(ev::io &watcher, int revents); - - virtual void log(const std::string &log) const; - -public: - UnixServer(const std::string &path); - - /* create the public HYDROGEN Driver endpoint over UNIX (local) domain. - * exit on failure - */ - void listen(); - - static std::string unixSocketPath; -}; - -std::string UnixServer::unixSocketPath = HYDROGENUNIXSOCK; - -#endif \ No newline at end of file diff --git a/modules/hydrogen_loader/xml_util.cpp b/modules/hydrogen_loader/xml_util.cpp deleted file mode 100644 index 3568d0c2..00000000 --- a/modules/hydrogen_loader/xml_util.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "xml_util.hpp" - -#include - -#include "time.hpp" -#include "hydrogen_server.hpp" - -int xmlReplacementMapFind(void *self, XMLEle *source, XMLEle **replace) -{ - auto map = (const std::unordered_map *)self; - auto idx = map->find(source); - if (idx == map->end()) - { - return 0; - } - *replace = (XMLEle *)idx->second; - return 1; -} - -XMLEle *cloneXMLEleWithReplacementMap(XMLEle *root, const std::unordered_map &replacement) -{ - return cloneXMLEle(root, &xmlReplacementMapFind, (void *)&replacement); -} - - -std::vector findBlobElements(XMLEle *root) -{ - std::vector result; - for (auto ep = nextXMLEle(root, 1); ep; ep = nextXMLEle(root, 0)) - { - if (strcmp(tagXMLEle(ep), "oneBLOB") == 0) - { - result.push_back(ep); - } - } - return result; -} - -/* log message in root known to be from device dev to ldir, if any. - */ -void logDMsg(XMLEle *root, const char *dev) -{ - char stamp[64]; - char logfn[1024]; - const char *ts, *ms; - FILE *fp; - - /* get message, if any */ - ms = findXMLAttValu(root, "message"); - if (!ms[0]) - return; - - /* get timestamp now if not provided */ - ts = findXMLAttValu(root, "timestamp"); - if (!ts[0]) - { - hydrogen_tstamp(stamp); - ts = stamp; - } - - /* append to log file, name is date portion of time stamp */ - sprintf(logfn, "%s/%.10s.islog", ldir, ts); - fp = fopen(logfn, "a"); - if (!fp) - return; /* oh well */ - fprintf(fp, "%s: %s: %s\n", ts, dev, ms); - fclose(fp); -} \ No newline at end of file diff --git a/modules/hydrogen_loader/xml_util.hpp b/modules/hydrogen_loader/xml_util.hpp deleted file mode 100644 index 30e5705b..00000000 --- a/modules/hydrogen_loader/xml_util.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#if ENABLE_FASTHASH -#include "emhash/hash_table8.hpp" -#else -#include -#endif - -#include "lilxml.hpp" - -int xmlReplacementMapFind(void *self, XMLEle *source, XMLEle **replace); - -XMLEle *cloneXMLEleWithReplacementMap(XMLEle *root, const std::unordered_map &replacement); - -std::vector findBlobElements(XMLEle *root); - -/* log message in root known to be from device dev to ldir, if any. - */ -void logDMsg(XMLEle *root, const char *dev); \ No newline at end of file diff --git a/modules/lithium_image/CMakeLists.txt b/modules/lithium_image/CMakeLists.txt deleted file mode 100644 index 822f991e..00000000 --- a/modules/lithium_image/CMakeLists.txt +++ /dev/null @@ -1,126 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(lithium_image C CXX) - -option(ENABLE_DEBUG "Enable Debug Mode" OFF) -if(ENABLE_DEBUG) - set(ENABLE_DEBUG_FLAG "1") -endif() - -option(ENABLE_OPENCV_FLAG "Enable OpenCV" OFF) -if (ENABLE_OPENCV_FLAG) - find_package(OpenCV REQUIRED) - message("-- Found OpenCV ${OpenCV_VERSION}: ${OpenCV_LIBRARIES}") -else() - message("-- OpenCV Not Found. Will not build opencv image module.") -endif() - - -option(ENABLE_CIMG_FLAG "Enable CImg" On) -if(ENABLE_CIMG_FLAG) - set(ENABLE_CIMG "1") -endif() - -option(ENABLE_STB_FLAG "Enable STB" OFF) -if(ENABLE_STB_FLAG) - set(ENABLE_STB "1") -endif() - -configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -CHECK_INCLUDE_FILE(format HAS_STD_FORMAT) - -if(NOT HAS_STD_FORMAT) - find_package(fmt REQUIRED) -endif() - -list(APPEND ${PROJECT_NAME}_LIBS - lithiumpluginstatic - loguru - ${CFITSIO_LIBRARIES} -) - -if(ENABLE_OPENCV_FLAG) - list(APPEND ${PROJECT_NAME}_LIBS - ${OpenCV_LIBRARIES} - ) -endif() -# Sources -list(APPEND ${PROJECT_NAME}_SOURCES - main.cpp -) - -if(ENABLE_OPENCV_FLAG) - list(APPEND ${PROJECT_NAME}_SOURCES - opencv/image.cpp - ) -endif() - -if(ENABLE_CIMG_FLAG) - list(APPEND ${PROJECT_NAME}_SOURCES - cimg/image.cpp - ) -endif() - -if(ENABLE_OPENCV_FLAG) - list(APPEND ${PROJECT_NAME}_HEADERS - opencv/image.hpp - ) -endif() - -if(ENABLE_CIMG_FLAG) - list(APPEND ${PROJECT_NAME}_HEADERS - cimg/image.hpp - ) -endif() - -# Private Headers -list(APPEND ${PROJECT_NAME}_PRIVATE_HEADERS - -) - -# Build Object Library -add_library(${PROJECT_NAME}_OBJECT OBJECT) -set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) - -target_link_libraries(${PROJECT_NAME}_OBJECT loguru lithiumpluginstatic) - -target_sources(${PROJECT_NAME}_OBJECT - PUBLIC - ${${PROJECT_NAME}_HEADERS} - PRIVATE - ${${PROJECT_NAME}_SOURCES} - ${${PROJECT_NAME}_PRIVATE_HEADERS} -) - -target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) - -add_library(${PROJECT_NAME}static STATIC) - -target_link_libraries(${PROJECT_NAME}static ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -target_link_libraries(${PROJECT_NAME}static ${CMAKE_THREAD_LIBS_INIT}) -target_include_directories(${PROJECT_NAME}static PUBLIC .) - -set_target_properties(${PROJECT_NAME}static PROPERTIES - VERSION ${CMAKE_HYDROGEN_VERSION_STRING} - SOVERSION ${HYDROGEN_SOVERSION} - OUTPUT_NAME ${PROJECT_NAME} # this same name like shared library - backwards compatibility -) - -install(TARGETS ${PROJECT_NAME}static - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) - -add_library(${PROJECT_NAME}shared SHARED) - -target_link_libraries(${PROJECT_NAME}shared ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -target_link_libraries(${PROJECT_NAME}shared ${CMAKE_THREAD_LIBS_INIT}) -target_include_directories(${PROJECT_NAME}shared PUBLIC .) -set_target_properties(${PROJECT_NAME}shared PROPERTIES - VERSION ${CMAKE_HYDROGEN_VERSION_STRING} - SOVERSION ${HYDROGEN_SOVERSION} - OUTPUT_NAME ${PROJECT_NAME} # this same name like shared library - backwards compatibility -) -install(TARGETS ${PROJECT_NAME}shared - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} -) \ No newline at end of file diff --git a/modules/lithium_image/cimg/draw.cpp b/modules/lithium_image/cimg/draw.cpp deleted file mode 100644 index 96e021c3..00000000 --- a/modules/lithium_image/cimg/draw.cpp +++ /dev/null @@ -1,895 +0,0 @@ -/** - * Star recognizer using the CImg library and CCFits. - * - * Copyright (C) 2015 Carsten Schmitt - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include -#include -#include "cimg/CImg.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "image.hpp" - -using namespace std; -using namespace cimg_library; - -namespace Lithium -{ - typedef tuple - PixelPosT; - typedef set PixelPosSetT; - typedef list PixelPosListT; - typedef tuple PixSubPosT; - typedef tuple - FrameT; - struct StarInfoT - { - FrameT clusterFrame; - FrameT cogFrame; - FrameT hfdFrame; - PixSubPosT cogCentroid; - PixSubPosT subPixelInterpCentroid; - float hfd; - float fwhmHorz; - float fwhmVert; - float maxPixValue; - bool saturated; - }; - typedef list StarInfoListT; - /** - * Get all pixels inside a radius: https://stackoverflow.com/questions/14487322/get-all-pixel-array-inside-circle - * Algorithm: https://en.wikipedia.org/wiki/Midpoint_circle_algorithm - */ - bool insideCircle(float inX - /*pos of x*/ - , - float inY - /*pos of y*/ - , - float inCenterX, float inCenterY, float inRadius) - { - return (pow(inX - inCenterX, 2.0) + pow(inY - inCenterY, 2.0) <= pow(inRadius, 2.0)); - } -#include - bool readFile(CImg &inImg, const string &inFilename, int *outBitPix = 0) - { - int status = 0; - fitsfile *fptr; - // Open the FITS file - fits_open_file(&fptr, inFilename.c_str(), READONLY, &status); - if (status != 0) - { - spdlog::error("Error opening file: {}", inFilename); - return false; - } - // Get the image dimensions and bitpix - int naxis; - long naxes[2]; - fits_get_img_param(fptr, 2, outBitPix, &naxis, naxes, &status); - if (status != 0) - { - spdlog::error("Error getting image parameters"); - fits_close_file(fptr, &status); - return false; - } - // Allocate memory for the image data - float *imgData = new float[naxes[0] * naxes[1]]; - // Read the image data - int anyNull; - long fpixel[2] = { - 1, 1}; - fits_read_pix(fptr, TFLOAT, fpixel, naxes[0] * naxes[1], NULL, imgData, &anyNull, &status); - if (status != 0) - { - spdlog::error("Error reading image data"); - delete[] imgData; - fits_close_file(fptr, &status); - return false; - } - // Copy the image data to the CImg object - inImg.resize(naxes[0], naxes[1], 1, 1); - cimg_forXY(inImg, x, y) - { - inImg(x, inImg.height() - y - 1) = imgData[x + y * naxes[0]]; - } - // Free memory used for image data - delete[] imgData; - // Close the FITS file - fits_close_file(fptr, &status); - if (status != 0) - { - spdlog::error("Error closing file: {}", inFilename); - return false; - } - return true; - } - template - void thresholdOtsu(const CImg &inImg, long inBitPix, CImg *outBinImg) - { - CImg<> hist = inImg.get_histogram(pow(2.0, inBitPix)); - float sum = 0; - cimg_forX(hist, pos) - { - sum += pos * hist[pos]; - } - float numPixels = inImg.width() * inImg.height(); - float sumB = 0, wB = 0, max = 0.0; - float threshold1 = 0.0, threshold2 = 0.0; - cimg_forX(hist, i) - { - wB += hist[i]; - if (!wB) - { - continue; - } - float wF = numPixels - wB; - if (!wF) - { - break; - } - sumB += i * hist[i]; - float mF = (sum - sumB) / wF; - float mB = sumB / wB; - float diff = mB - mF; - float bw = wB * wF * pow(diff, 2.0); - if (bw >= max) - { - threshold1 = i; - if (bw > max) - { - threshold2 = i; - } - max = bw; - } - } - // end loop - float th = (threshold1 + threshold2) / 2.0; - *outBinImg = inImg; - // Create a copy - outBinImg->threshold(th); - DLOG_F(INFO,"Threshold Otsu finished"); - } - /** - * Removes all white neighbours around pixel from whitePixels - * if they exist and adds them to pixelsToBeProcessed. - */ - void getAndRemoveNeighbours(PixelPosT inCurPixelPos, PixelPosSetT *inoutWhitePixels, PixelPosListT *inoutPixelsToBeProcessed) - { - const size_t _numPixels = 8, _x = 0, _y = 1; - const int offsets[_numPixels][2] = {{-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1}}; - for (size_t p = 0; p < _numPixels; ++p) - { - PixelPosT curPixPos(std::get<0>(inCurPixelPos) + offsets[p][_x], std::get<1>(inCurPixelPos) + offsets[p][_y]); - PixelPosSetT::iterator itPixPos = inoutWhitePixels->find(curPixPos); - if (itPixPos != inoutWhitePixels->end()) - { - const PixelPosT &curPixPos = *itPixPos; - inoutPixelsToBeProcessed->push_back(curPixPos); - inoutWhitePixels->erase(itPixPos); - // Remove white pixel from "white set" since it has been now processed - DLOG_F(INFO,"Removed neighbor pixel at ({}, {})", std::get<0>(curPixPos), std::get<1>(curPixPos)); - } - } - DLOG_F(INFO,"Finished removing neighbours"); - } - template - void clusterStars(const CImg &inImg, StarInfoListT *outStarInfos) - { - PixelPosSetT whitePixels; - cimg_forXY(inImg, x, y) - { - if (inImg(x, y)) - { - whitePixels.insert(whitePixels.end(), PixelPosT(x, y)); - } - } - // Iterate over white pixels as long as set is not empty - while (whitePixels.size()) - { - PixelPosListT pixelsToBeProcessed; - PixelPosSetT::iterator itWhitePixPos = whitePixels.begin(); - pixelsToBeProcessed.push_back(*itWhitePixPos); - whitePixels.erase(itWhitePixPos); - FrameT frame(inImg.width(), inImg.height(), 0, 0); - while (!pixelsToBeProcessed.empty()) - { - PixelPosT curPixelPos = pixelsToBeProcessed.front(); - // Determine boundaries (min max in x and y directions) - if (std::get<0>(curPixelPos) < std::get<0>(frame)) - { - std::get<0>(frame) = std::get<0>(curPixelPos); - } - if (std::get<0>(curPixelPos) > std::get<2>(frame)) - { - std::get<2>(frame) = std::get<0>(curPixelPos); - } - if (std::get<1>(curPixelPos) < std::get<1>(frame)) - { - std::get<1>(frame) = std::get<1>(curPixelPos); - } - if (std::get<1>(curPixelPos) > std::get<3>(frame)) - { - std::get<3>(frame) = std::get<1>(curPixelPos); - } - getAndRemoveNeighbours(curPixelPos, &whitePixels, &pixelsToBeProcessed); - pixelsToBeProcessed.pop_front(); - } - // Create new star-info and set cluster-frame. - StarInfoT starInfo; - starInfo.clusterFrame = frame; - outStarInfos->push_back(starInfo); - DLOG_F(INFO,"Cluster added with frame ({}, {}, {}, {})", std::get<0>(frame), std::get<1>(frame), std::get<2>(frame), std::get<3>(frame)); - } - DLOG_F(INFO,"Clustering stars finished"); - } - float calcIx2(const CImg &img, int x) - { - float Ix = 0; - cimg_forY(img, y) - { - Ix += pow(img(x, y), 2.0) * (float)x; - } - return Ix; - } - float calcJy2(const CImg &img, int y) - { - float Iy = 0; - cimg_forX(img, x) - { - Iy += pow(img(x, y), 2.0) * (float)y; - } - return Iy; - } - // Calculate Intensity Weighted Center (IWC) - void calcIntensityWeightedCenter(const CImg &inImg, float *outX, float *outY) - { - assert(outX && outY); - // Determine weighted centroid - See https://cdn.intechopen.com/pdfs-wm/26716.pdf - float Imean2 = 0, Jmean2 = 0, Ixy2 = 0; - for (size_t i = 0; i < inImg.width(); ++i) - { - Imean2 += calcIx2(inImg, i); - cimg_forY(inImg, y) - { - Ixy2 += pow(inImg(i, y), 2.0); - } - } - for (size_t i = 0; i < inImg.height(); ++i) - { - Jmean2 += calcJy2(inImg, i); - } - *outX = Imean2 / Ixy2; - *outY = Jmean2 / Ixy2; - } - void calcSubPixelCenter(const CImg &inImg, float *outX, float *outY, size_t inNumIter = 10) - /*num iterations*/ { - assert(inImg.width() == 3 && inImg.height() == 3); - // Sub pixel interpolation - float c, a1, a2, a3, a4, b1, b2, b3, b4; - float a1n, a2n, a3n, a4n, b1n, b2n, b3n, b4n; - b1 = inImg(0, 0); - a2 = inImg(1, 0); - b2 = inImg(2, 0); - a1 = inImg(0, 1); - c = inImg(1, 1); - a3 = inImg(2, 1); - b4 = inImg(0, 2); - a4 = inImg(1, 2); - b3 = inImg(2, 2); - for (size_t i = 0; i < inNumIter; ++i) - { - float c2 = 2 * c; - float sp1 = (a1 + a2 + c2) / 4; - float sp2 = (a2 + a3 + c2) / 4; - float sp3 = (a3 + a4 + c2) / 4; - float sp4 = (a4 + a1 + c2) / 4; - // New maximum is center - float newC = std::max({sp1, sp2, sp3, sp4}); - // Calc position of new center - float ad = pow(2.0, -((float)i + 1)); - if (newC == sp1) - { - *outX = *outX - ad; - // to the left - *outY = *outY - ad; - // to the top - // Calculate new sub pixel values - b1n = (a1 + 2 * a2 + c) / 4; - b2n = (a2 + 2 * c + 2 * a3 + b2) / 4; - b3n = (c + 2 * a3 + b4 + 2 * a4) / 4; - a4n = (a4 + 2 * c + 2 * a1 + b4) / 4; - } - else if (newC == sp2) - { - *outY = *outY - ad; - // to the top - // Calculate new sub pixel values - a1n = (b1 + 2 * a1 + c) / 4; - b2n = (a2 + 2 * a1 + 2 * c + b2) / 4; - a3n = (a3 + 2 * c + 2 * a2 + b2) / 4; - b3n = (a3 + 2 * c + b4) / 4; - b4n = (a4 + 2 * a3 + 2 * c + b4) / 4; - } - else if (newC == sp3) - { - *outX = *outX + ad; - // to the right - *outY = *outY - ad; - // to the top - // Calculate new sub pixel values - b1n = (2 * a1 + c + b2) / 4; - a2n = (a2 + 2 * c + 2 * a1 + b2) / 4; - b3n = (2 * a3 + c + b4) / 4; - a4n = (a4 + 2 * c + 2 * a3 + b4) / 4; - b4n = (a4 + 2 * a3 + c) / 4; - } - else - { - *outX = *outX + ad; - // to the right - // Calculate new sub pixel values - a1n = (a1 + 2 * c + 2 * a2 + b2) / 4; - b1n = (a1 + 2 * a2 + c) / 4; - b2n = (a2 + 2 * c + 2 * a3 + b2) / 4; - a4n = (b4 + 2 * a3 + 2 * c + a4) / 4; - b3n = (a3 + 2 * c + b4) / 4; - } - // Set new pixel values - if (newC == sp1) - { - a1 = a1n; - b1 = b1n; - } - else if (newC == sp2) - { - a2 = a2n; - b2 = b2n; - } - else if (newC == sp3) - { - a3 = a3n; - b3 = b3n; - } - else - { - a4 = a4n; - b4 = b4n; - } - } - *outX += 1.0f; - // add one since we are currently at pixel (1, 1) - *outY += 1.0f; - } - void calcCentroid(const CImg &inImg, const FrameT &inFrame, PixSubPosT *outPixelPos, PixSubPosT *outSubPixelPos = nullptr, size_t inNumIterations = 10) - { - // Get frame sub img - CImg subImg = inImg.get_crop(std::get<0>(inFrame), std::get<1>(inFrame), std::get<2>(inFrame), std::get<3>(inFrame)); - float &xc = std::get<0>(*outPixelPos); - float &yc = std::get<1>(*outPixelPos); - // 1. Calculate the IWC - calcIntensityWeightedCenter(subImg, &xc, &yc); - DLOG_F(INFO,"IWC: ({}, {})", xc, yc); - if (outSubPixelPos) - { - // 2. Round to nearest integer and then iteratively improve. - int xi = floor(xc + 0.5); - int yi = floor(yc + 0.5); - DLOG_F(INFO,"Integer pixel position: ({}, {})", xi, yi); - CImg img3x3 = inImg.get_crop(xi - 1, yi - 1, xi + 1, yi + 1); - // 3. Interpolate using sub-pixel algorithm - float xsc = xi, ysc = yi; - calcSubPixelCenter(img3x3, &xsc, &ysc, inNumIterations); - DLOG_F(INFO,"Sub-pixel position: ({}, {})", xsc, ysc); - std::get<0>(*outSubPixelPos) = xsc; - std::get<1>(*outSubPixelPos) = ysc; - } - } - /** - * Expects star centered in the middle of the image (in x and y) and mean background subtracted from image. - * - * HDF calculation: https://www005.upp.so-net.ne.jp/k_miyash/occ02/halffluxdiameter/halffluxdiameter_en.html - * https://www.cyanogen.com/help/maximdl/Half-Flux.htm - * - * NOTE: Currently the accuracy is limited by the insideCircle function (-> sub-pixel accuracy). - * NOTE: The HFD is estimated in case there is no flux (HFD ~ sqrt(2) * inOuterDiameter / 2). - * NOTE: The outer diameter is usually a value which depends on the properties of the optical - * system and also on the seeing conditions. The HFD value calculated depends on this - * outer diameter value. - */ - float calcHfd(const CImg &inImage, unsigned int inOuterDiameter) - { - float outerRadius = inOuterDiameter / 2; - float sum = 0, sumDist = 0; - int centerX = ceil(inImage.width() / 2.0); - int centerY = ceil(inImage.height() / 2.0); - cimg_forXY(inImage, x, y) - { - if (insideCircle(x, y, centerX, centerY, outerRadius)) - { - sum += inImage(x, y); - sumDist += inImage(x, y) * sqrt(pow((float)x - (float)centerX, 2.0f) + pow((float)y - (float)centerY, 2.0f)); - } - } - // NOTE: Multiplying with 2 is required since actually just the HFR is calculated above - float hfd = (sum ? 2.0 * sumDist / sum : sqrt(2.0) * outerRadius); - DLOG_F(INFO,"HFD: {}", hfd); - return hfd; - } - /********************************************************************** - * Helper classes - **********************************************************************/ - struct DataPointT - { - float x; - float y; - DataPointT(float inX = 0, float inY = 0) : x(inX), y(inY) - { - } - // 添加调试日志 - void debug() const - { - DLOG_F(INFO,"Data point: ({}, {})", x, y); - } - }; - typedef std::vector DataPointsT; - struct GslMultiFitDataT - { - float y; - float sigma; - DataPointT pt; - }; - typedef std::vector GslMultiFitParmsT; - template - class CurveFitTmplT - { - public: - typedef typename FitTraitsT::CurveParamsT CurveParamsT; - template - static int fitGslLevenbergMarquart(const typename DataAccessorT::TypeT &inData, - typename CurveParamsT::TypeT *outResults, - double inEpsAbs, double inEpsRel, size_t inNumMaxIter = 500) - { - // 添加调试日志 - DLOG_F(INFO,"Fitting GSL Levenberg-Marquart"); - DLOG_F(INFO,"inEpsAbs: {}, inEpsRel: {}, inNumMaxIter: {}", inEpsAbs, inEpsRel, inNumMaxIter); - // 填充参数 - GslMultiFitParmsT gslMultiFitParms(inData.size()); - for (typename DataAccessorT::TypeT::const_iterator it = inData.begin(); it != inData.end(); ++it) - { - size_t idx = std::distance(inData.begin(), it); - const DataPointT &dataPoint = DataAccessorT::getDataPoint(idx, it); - gslMultiFitParms[idx].y = dataPoint.y; - gslMultiFitParms[idx].sigma = 0.1f; - gslMultiFitParms[idx].pt = dataPoint; - // 添加调试日志 - dataPoint.debug(); - } - // 填充函数信息 - gsl_multifit_function_fdf f; - f.f = FitTraitsT::gslFx; - f.df = FitTraitsT::gslDfx; - f.fdf = FitTraitsT::gslFdfx; - f.n = inData.size(); - f.p = FitTraitsT::CurveParamsT::_Count; - f.params = &gslMultiFitParms; - // 分配初始猜测向量并填充,替换掉多余的变量 - gsl_vector *guess = gsl_vector_alloc(f.p); - FitTraitsT::makeGuess(gslMultiFitParms, guess); - // 创建一个Levenberg-Marquardt求解器,n个数据点和m个参数 - gsl_multifit_fdfsolver *solver = gsl_multifit_fdfsolver_alloc(gsl_multifit_fdfsolver_lmsder, f.n, f.p); - // 初始化求解器 - gsl_multifit_fdfsolver_set(solver, &f, guess); - // 迭代直到找到结果 - int status, i = 0; - do - { - i++; - // 迭代一次 - status = gsl_multifit_fdfsolver_iterate(solver); - if (status != GSL_SUCCESS) - { - break; - } - // 检查迭代误差 - status = gsl_multifit_test_delta(solver->dx, solver->x, inEpsAbs, inEpsRel); - DLOG_F(INFO,"Iteration {}: dx norm = {}, x norm = {}, f norm = {}", i, gsl_blas_dnrm2(solver->dx), - gsl_blas_dnrm2(solver->x), gsl_blas_dnrm2(solver->f)); - } while (status == GSL_CONTINUE && i < inNumMaxIter); - // 存储结果供用户返回(从gsl_vector复制到结果结构) - for (size_t i = 0; i < CurveParamsT::_Count; ++i) - { - typename CurveParamsT::TypeE idx = static_cast(i); - (*outResults)[idx] = gsl_vector_get(solver->x, idx); - } - // 释放内存 - gsl_multifit_fdfsolver_free(solver); - gsl_vector_free(guess); - return status; - } - }; - /********************************************************************** - * Gaussian fit traits - **********************************************************************/ - class GaussianFitTraitsT - { - private: - public: - struct CurveParamsT - { - // b = base, p = peak, c = center in x, w = mean width (FWHM) - enum TypeE - { - B_IDX = 0, - P_IDX, - C_IDX, - W_IDX, - _Count - }; - struct TypeT : public std::array - { - TypeT(const gsl_vector *inVec = 0) - { - for (size_t i = 0; i < TypeE::_Count; ++i) - { - TypeE idx = static_cast(i); - (*this)[i] = (inVec ? gsl_vector_get(inVec, idx) : 0); - } - } - }; - }; - /* Makes a guess for b, p, c and w based on the supplied data */ - static void makeGuess(const GslMultiFitParmsT &inData, gsl_vector *guess) - { - size_t numDataPoints = inData.size(); - float y_mean = 0; - float y_max = inData.at(0).pt.y; - float c = inData.at(0).pt.x; - for (size_t i = 0; i < numDataPoints; ++i) - { - const DataPointT &dataPoint = inData.at(i).pt; - y_mean += dataPoint.y; - if (y_max < dataPoint.y) - { - y_max = dataPoint.y; - c = dataPoint.x; - } - } - y_mean /= (float)numDataPoints; - float w = (inData.at(numDataPoints - 1).pt.x - inData.at(0).pt.x) / 10.0; - gsl_vector_set(guess, CurveParamsT::B_IDX, y_mean); - gsl_vector_set(guess, CurveParamsT::P_IDX, y_max); - gsl_vector_set(guess, CurveParamsT::C_IDX, c); - gsl_vector_set(guess, CurveParamsT::W_IDX, w); - } - /* y = b + p * exp(-0.5f * ((t - c) / w) * ((t - c) / w)) */ - static float fx(float x, const CurveParamsT::TypeT &inParms) - { - float b = inParms[CurveParamsT::B_IDX]; - float p = inParms[CurveParamsT::P_IDX]; - float c = inParms[CurveParamsT::C_IDX]; - float w = inParms[CurveParamsT::W_IDX]; - float t = ((x - c) / w); - t *= t; - return (b + p * exp(-0.5f * t)); - } - /* Calculates f(x) = b + p * e^[0.5*((x-c)/w)] for each data point. */ - static int gslFx(const gsl_vector *x, void *inGslParams, gsl_vector *outResultVec) - { - CurveParamsT::TypeT curveParams(x); - // Store the current coefficient values - const GslMultiFitParmsT *gslParams = ((GslMultiFitParmsT *)inGslParams); - // Store parameter values - // Execute Levenberg-Marquart on f(x) - for (size_t i = 0; i < gslParams->size(); ++i) - { - const GslMultiFitDataT &gslData = gslParams->at(i); - float yi = GaussianFitTraitsT::fx((float)gslData.pt.x, curveParams); - gsl_vector_set(outResultVec, i, (yi - gslData.y) / gslData.sigma); - } - return GSL_SUCCESS; - } - /* Calculates the Jacobian (derivative) matrix of f(x) = b + p * e^[0.5*((x-c)/w)^2] for each data point */ - static int gslDfx(const gsl_vector *x, void *params, gsl_matrix *J) - { - // Store parameter values - const GslMultiFitParmsT *gslParams = ((GslMultiFitParmsT *)params); - // Store current coefficients - float p = gsl_vector_get(x, CurveParamsT::P_IDX); - float c = gsl_vector_get(x, CurveParamsT::C_IDX); - float w = gsl_vector_get(x, CurveParamsT::W_IDX); - // Store non-changing calculations - float w2 = w * w; - float w3 = w2 * w; - for (size_t i = 0; i < gslParams->size(); ++i) - { - const GslMultiFitDataT &gslData = gslParams->at(i); - float x_minus_c = (gslData.pt.x - c); - float e = exp(-0.5f * (x_minus_c / w) * (x_minus_c / w)); - gsl_matrix_set(J, i, CurveParamsT::B_IDX, 1 / gslData.sigma); - gsl_matrix_set(J, i, CurveParamsT::P_IDX, e / gslData.sigma); - gsl_matrix_set(J, i, CurveParamsT::C_IDX, (p * e * x_minus_c) / (gslData.sigma * w2)); - gsl_matrix_set(J, i, CurveParamsT::W_IDX, (p * e * x_minus_c * x_minus_c) / (gslData.sigma * w3)); - } - return GSL_SUCCESS; - } - /* Invokes f(x) and f'(x) */ - static int gslFdfx(const gsl_vector *x, void *params, gsl_vector *f, gsl_matrix *J) - { - DLOG_F(INFO,"gslFdfx"); - gslFx(x, params, f); - gslDfx(x, params, J); - return GSL_SUCCESS; - } - }; - typedef list MyDataContainerT; - class MyDataAccessorT - { - public: - typedef MyDataContainerT TypeT; - static DataPointT getDataPoint(size_t inIdx, TypeT::const_iterator inIt) - { - const PixSubPosT &pos = *inIt; - DataPointT dp(get<0>(pos), get<1>(pos)); - return dp; - } - }; - FrameT rectify(const FrameT &inFrame) - { - float border = 3; - float border2 = 2.0 * border; - float width = fabs(std::get<0>(inFrame) - std::get<2>(inFrame)) + border2; - float height = fabs(std::get<1>(inFrame) - std::get<3>(inFrame)) + border2; - float L = max(width, height); - float x0 = std::get<0>(inFrame) - (fabs(width - L) / 2.0) - border; - float y0 = std::get<1>(inFrame) - (fabs(height - L) / 2.0) - border; - return FrameT(x0, y0, x0 + L, y0 + L); - } - int StarDrawing(const std::string &filename, const unsigned int outerHfdDiameter) - { - /* outerHfdDiameter depends on pixel size and focal length (and seeing...). - Later we may calculate it automatically wihth goven focal length and pixel - size of the camera. For now it is a "best guess" value. - */ - StarInfoListT starInfos; - vector> starBuckets; - CImg img; - int bitPix = 0; - // Read file to CImg - try - { - readFile(img, filename, &bitPix); - } - catch (std::runtime_error &) - { - cerr << "Read FITS failed." << endl; - return 1; - } - // Create RGB image from fits file to paint boundaries and centroids (just for visualization) - CImg rgbImg(img.width(), img.height(), 1 - /*depth*/ - , - 3 - /*3 channels - RGB*/ - ); - float min = img.min(), mm = img.max() - min; - cimg_forXY(img, x, y) - { - int value = 255.0 * (img(x, y) - min) / mm; - rgbImg(x, y, 0 - /*red*/ - ) = value; - rgbImg(x, y, 1 - /*green*/ - ) = value; - rgbImg(x, y, 2 - /*blue*/ - ) = value; - } - CImg &aiImg = img.blur_anisotropic(30.0f, - /*amplitude*/ - 0.5f, - /*sharpness*/ - 0.3f, - /*anisotropy*/ - 0.6f, - /*alpha*/ - 1.1f, - /*sigma*/ - 0.8f, - /*dl*/ - 30, - /*da*/ - 2, - /*gauss_prec*/ - 0, - /*interpolation_type*/ - false - /*fast_approx*/ - ); - // Thresholding (Otsu) --> In: Noise reduced image, Out: binary image - CImg binImg; - thresholdOtsu(aiImg, bitPix, &binImg); - // Clustering --> In: binary image from thresholding, Out: List of detected stars, subimg-boundaries (x1,y1,x2,y2) for each star - clusterStars(binImg, &starInfos); - cerr << "Recognized " << starInfos.size() << " stars..." << endl; - // Calc brightness boundaries for possible focusing stars - float maxPossiblePixValue = pow(2.0, bitPix) - 1; - // For each star - for (auto &starInfo : starInfos) - { - const FrameT &frame = starInfo.clusterFrame; - FrameT &cogFrame = starInfo.cogFrame; - FrameT &hfdFrame = starInfo.hfdFrame; - PixSubPosT &cogCentroid = starInfo.cogCentroid; - PixSubPosT &subPixelInterpCentroid = starInfo.subPixelInterpCentroid; - float &hfd = starInfo.hfd; - float &fwhmHorz = starInfo.fwhmHorz; - float &fwhmVert = starInfo.fwhmVert; - float &maxPixValue = starInfo.maxPixValue; - bool &saturated = starInfo.saturated; - FrameT squareFrame = rectify(frame); - calcCentroid(aiImg, squareFrame, &cogCentroid, &subPixelInterpCentroid, 10); - std::get<0>(cogCentroid) += std::get<0>(squareFrame); - std::get<1>(cogCentroid) += std::get<1>(squareFrame); - DLOG_F(INFO,"Cluster frame: ({}, {}, {}, {})", std::get<0>(frame), std::get<1>(frame), std::get<2>(frame), std::get<3>(frame)); - DLOG_F(INFO,"Square frame: ({}, {}, {}, {})", std::get<0>(squareFrame), std::get<1>(squareFrame), std::get<2>(squareFrame), std::get<3>(squareFrame)); - DLOG_F(INFO,"COG centroid: ({}, {})", std::get<0>(cogCentroid), std::get<1>(cogCentroid)); - float maxClusterEdge = std::max(fabs(std::get<0>(frame) - std::get<2>(frame)), fabs(std::get<1>(frame) - std::get<3>(frame))); - float cogHalfEdge = ceil(maxClusterEdge / 2.0); - float cogX = std::get<0>(cogCentroid); - float cogY = std::get<1>(cogCentroid); - std::get<0>(cogFrame) = cogX - cogHalfEdge - 1; - std::get<1>(cogFrame) = cogY - cogHalfEdge - 1; - std::get<2>(cogFrame) = cogX + cogHalfEdge + 1; - std::get<3>(cogFrame) = cogY + cogHalfEdge + 1; - DLOG_F(INFO,"COG frame: ({}, {}, {}, {})", std::get<0>(cogFrame), std::get<1>(cogFrame), std::get<2>(cogFrame), std::get<3>(cogFrame)); - size_t hfdRectDist = floor(outerHfdDiameter / 2.0); - std::get<0>(hfdFrame) = cogX - hfdRectDist; - std::get<1>(hfdFrame) = cogY - hfdRectDist; - std::get<2>(hfdFrame) = cogX + hfdRectDist; - std::get<3>(hfdFrame) = cogY + hfdRectDist; - CImg hfdSubImg = aiImg.get_crop(std::get<0>(hfdFrame), std::get<1>(hfdFrame), std::get<2>(hfdFrame), std::get<3>(hfdFrame)); - maxPixValue = hfdSubImg.max(); - saturated = (maxPixValue == maxPossiblePixValue); - CImg imgHfdSubMean(hfdSubImg); - double mean = hfdSubImg.mean(); - cimg_forXY(hfdSubImg, x, y) - { - imgHfdSubMean(x, y) = (hfdSubImg(x, y) < mean ? 0 : hfdSubImg(x, y) - mean); - } - hfd = calcHfd(imgHfdSubMean, outerHfdDiameter); - DLOG_F(INFO,"HFD: {}", hfd); - MyDataContainerT vertDataPoints, horzDataPoints; - cimg_forX(imgHfdSubMean, x) - { - horzDataPoints.push_back(make_pair(x, imgHfdSubMean(x, floor(imgHfdSubMean.height() / 2.0 + 0.5)))); - } - cimg_forY(imgHfdSubMean, y) - { - vertDataPoints.push_back(make_pair(y, imgHfdSubMean(floor(imgHfdSubMean.width() / 2.0 + 0.5), y))); - } - typedef CurveFitTmplT GaussMatcherT; - typedef GaussMatcherT::CurveParamsT CurveParamsT; - CurveParamsT::TypeT gaussCurveParmsHorz, gaussCurveParmsVert; - GaussMatcherT::fitGslLevenbergMarquart(horzDataPoints, &gaussCurveParmsHorz, 0.1f, 0.1f); - fwhmHorz = gaussCurveParmsHorz[CurveParamsT::W_IDX]; - GaussMatcherT::fitGslLevenbergMarquart(vertDataPoints, &gaussCurveParmsVert, 0.1f, 0.1f); - fwhmVert = gaussCurveParmsVert[CurveParamsT::W_IDX]; - DLOG_F(INFO,"FWHM(horizontal): {}", fwhmHorz); - DLOG_F(INFO,"FWHM(vertical): {}", fwhmVert); - } - // Create result image - const int factor = 4; - CImg &rgbResized = rgbImg.resize(factor * rgbImg.width(), factor * rgbImg.height(), - -100 - /*size_z*/ - , - -100 - /*size_c*/ - , - 1 - /*interpolation_type*/ - ); - // Draw cluster boundaries and square cluster boundaries - const unsigned char red[3] = { - 255, 0, 0}, - green[3] = {0, 255, 0}, yellow[3] = {255, 255, 0}; - const unsigned char black[3] = { - 0, 0, 0}, - blue[3] = {0, 0, 255}, white[3] = {255, 255, 255}; - const size_t cCrossSize = 3; - // Mark all stars in RGB image - for (StarInfoListT::iterator it = starInfos.begin(); it != starInfos.end(); ++it) - { - StarInfoT *curStarInfo = &(*it); - PixSubPosT &cogCentroid = curStarInfo->cogCentroid; - float &hfd = curStarInfo->hfd; - float &fwhmHorz = curStarInfo->fwhmHorz; - float &fwhmVert = curStarInfo->fwhmVert; - float &maxPixValue = curStarInfo->maxPixValue; - DLOG_F(INFO,"cogCentroid=({},{}) maxPixValue: {} sat: {} hfd: {} fwhmHorz: {} fwhmVert: {}", - std::get<0>(curStarInfo->cogCentroid), std::get<1>(curStarInfo->cogCentroid), - maxPixValue, curStarInfo->saturated ? "Y" : "N", hfd, fwhmHorz, fwhmVert); - const FrameT &frame = curStarInfo->clusterFrame; - FrameT squareFrame(rectify(frame)); - // Draw centroid crosses and centroid boundaries - const PixSubPosT &subPos = curStarInfo->cogCentroid; - const FrameT &cogFrame = curStarInfo->cogFrame; - const FrameT &hfdFrame = curStarInfo->hfdFrame; - rgbResized.draw_line(floor(factor * (std::get<0>(subPos) - cCrossSize) + 0.5), floor(factor * std::get<1>(subPos) + 0.5), - floor(factor * (std::get<0>(subPos) + cCrossSize) + 0.5), floor(factor * std::get<1>(subPos) + 0.5), green, 1 - /*opacity*/ - ); - rgbResized.draw_line(floor(factor * std::get<0>(subPos) + 0.5), floor(factor * (std::get<1>(subPos) - cCrossSize) + 0.5), - floor(factor * std::get<0>(subPos) + 0.5), floor(factor * (std::get<1>(subPos) + cCrossSize) + 0.5), green, 1 - /*opacity*/ - ); - /* - */ - rgbResized.draw_rectangle(floor(factor * std::get<0>(cogFrame) + 0.5), floor(factor * std::get<1>(cogFrame) + 0.5), - floor(factor * std::get<2>(cogFrame) + 0.5), floor(factor * std::get<3>(cogFrame) + 0.5), - green, 1, - ~0); - // Draw text - const bool &saturated = curStarInfo->saturated; - ostringstream oss; - oss.precision(4); - oss << "HFD=" << hfd << endl - << "FWHM H=" << fwhmHorz << endl - << "FWHM V=" << fwhmVert << endl - << "MAX=" << (int)maxPixValue << endl - << "SAT=" << (saturated ? "Y" : "N"); - rgbImg.draw_text(floor(factor * std::get<0>(subPos) + 0.5), floor(factor * std::get<1>(subPos) + 0.5), oss.str().c_str(), white - /*fg color*/ - , - black - /*bg color*/ - , - 0.7 - /*opacity*/ - , - 9 - /*font-size*/ - ); - } - rgbResized.save_bmp("out.bmp"); - return 0; - } -} diff --git a/modules/lithium_image/cimg/image.cpp b/modules/lithium_image/cimg/image.cpp deleted file mode 100644 index e79b7085..00000000 --- a/modules/lithium_image/cimg/image.cpp +++ /dev/null @@ -1,674 +0,0 @@ -/* - * image.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-12-1 - -Description: Image processing plugin for Lithium - -**************************************************/ - -#include "image.hpp" - -#include -#include - -#include "atom/log/loguru.hpp" - -ImageProcessingPlugin::ImageProcessingPlugin(const std::string &path, const std::string &version, const std::string &author, const std::string &description) - : Plugin(path, version, author, description) -{ - // 在构造函数中注册图像处理函数 - RegisterFunc("blur", &ImageProcessingPlugin::Blur, this); - RegisterFunc("rotate", &ImageProcessingPlugin::Rotate, this); - RegisterFunc("crop", &ImageProcessingPlugin::Crop, this); - RegisterFunc("sharpen", &ImageProcessingPlugin::Sharpen, this); - RegisterFunc("white_balance", &ImageProcessingPlugin::WhiteBalance, this); - RegisterFunc("resize", &ImageProcessingPlugin::Resize, this); - RegisterFunc("blur", &ImageProcessingPlugin::Blur, this); - RegisterFunc("rotate", &ImageProcessingPlugin::Rotate, this); - RegisterFunc("image2base64", &ImageProcessingPlugin::ImageToBase64, this); - RegisterFunc("base642image", &ImageProcessingPlugin::Base64ToImage, this); -} - -void ImageProcessingPlugin::Blur(const json ¶ms) -{ - std::string imagePath = params[0].get(); - int radius = params[1].get(); - - // 检查缓存中是否存在图像 - if (imageCache.find(imagePath) != imageCache.end()) - { - // 从缓存中获取图像并进行模糊处理 - CImg image = imageCache[imagePath]; - image.blur(radius); - imageCache[imagePath] = image; - } - else - { - // 加载图像,并进行模糊处理 - CImg image(imagePath.c_str()); - image.blur(radius); - imageCache[imagePath] = image; - } - - // 在这里可以根据实际需求对图像进行进一步处理或保存 - // ... - - // 输出处理后的图像信息 - std::cout << "Image blurred: " << imagePath << std::endl; -} - -void ImageProcessingPlugin::Rotate(const json ¶ms) -{ - std::string imagePath = params[0].get(); - int angle = params[1].get(); - - // 检查缓存中是否存在图像 - if (imageCache.find(imagePath) != imageCache.end()) - { - // 从缓存中获取图像并进行旋转 - CImg image = imageCache[imagePath]; - image.rotate(angle); - imageCache[imagePath] = image; - } - else - { - // 加载图像,并进行旋转 - CImg image(imagePath.c_str()); - image.rotate(angle); - imageCache[imagePath] = image; - } - - // 在这里可以根据实际需求对图像进行进一步处理或保存 - // ... - - // 输出处理后的图像信息 - std::cout << "Image rotated: " << imagePath << std::endl; -} - -void ImageProcessingPlugin::Crop(const json ¶ms) -{ - std::string imagePath = params[0].get(); - int x = params[1].get(); - int y = params[2].get(); - int width = params[3].get(); - int height = params[4].get(); - - // 检查缓存中是否存在图像 - if (imageCache.find(imagePath) != imageCache.end()) - { - // 从缓存中获取图像并进行裁剪 - CImg image = imageCache[imagePath]; - image.crop(x, y, x + width - 1, y + height - 1); - imageCache[imagePath] = image; - } - else - { - // 加载图像,并进行裁剪 - CImg image(imagePath.c_str()); - image.crop(x, y, x + width - 1, y + height - 1); - imageCache[imagePath] = image; - } - - // 在这里可以根据实际需求对图像进行进一步处理或保存 - // ... - - // 输出处理后的图像信息 - std::cout << "Image cropped: " << imagePath << std::endl; -} - -void ImageProcessingPlugin::Sharpen(const json ¶ms) -{ - std::string imagePath = params[0].get(); - int factor = params[1].get(); - - // 检查缓存中是否存在图像 - if (imageCache.find(imagePath) != imageCache.end()) - { - // 从缓存中获取图像并进行锐化 - CImg image = imageCache[imagePath]; - image.sharpen(factor); - imageCache[imagePath] = image; - } - else - { - // 加载图像,并进行锐化 - CImg image(imagePath.c_str()); - image.sharpen(factor); - imageCache[imagePath] = image; - } - - // 在这里可以根据实际需求对图像进行进一步处理或保存 - // ... - - // 输出处理后的图像信息 - std::cout << "Image sharpened: " << imagePath << std::endl; -} - -void ImageProcessingPlugin::WhiteBalance(const json ¶ms) -{ - std::string imagePath = params[0].get(); - - // 检查缓存中是否存在图像 - if (imageCache.find(imagePath) != imageCache.end()) - { - // 从缓存中获取图像并进行白平衡调整 - CImg image = imageCache[imagePath]; - - // 计算每个通道的平均值 - double r = 0, g = 0, b = 0; - cimg_forXY(image, x, y) - { - r += image(x, y, 0); - g += image(x, y, 1); - b += image(x, y, 2); - } - int size = image.width() * image.height(); - r /= size; - g /= size; - b /= size; - - // 调整每个通道的白平衡 - cimg_forXY(image, x, y) - { - double factor_r = r / image(x, y, 0); - double factor_g = g / image(x, y, 1); - double factor_b = b / image(x, y, 2); - image(x, y, 0) = cimg::cut(image(x, y, 0) * factor_r, 0, 255); - image(x, y, 1) = cimg::cut(image(x, y, 1) * factor_g, 0, 255); - image(x, y, 2) = cimg::cut(image(x, y, 2) * factor_b, 0, 255); - } - - imageCache[imagePath] = image; - } - else - { - // 加载图像,并进行白平衡调整 - CImg image(imagePath.c_str()); - - // 计算每个通道的平均值 - double r = 0, g = 0, b = 0; - cimg_forXY(image, x, y) - { - r += image(x, y, 0); - g += image(x, y, 1); - b += image(x, y, 2); - } - int size = image.width() * image.height(); - r /= size; - g /= size; - b /= size; - - // 调整每个通道的白平衡 - cimg_forXY(image, x, y) - { - double factor_r = r / image(x, y, 0); - double factor_g = g / image(x, y, 1); - double factor_b = b / image(x, y, 2); - image(x, y, 0) = cimg::cut(image(x, y, 0) * factor_r, 0, 255); - image(x, y, 1) = cimg::cut(image(x, y, 1) * factor_g, 0, 255); - image(x, y, 2) = cimg::cut(image(x, y, 2) * factor_b, 0, 255); - } - - imageCache[imagePath] = image; - } - - // 在这里可以根据实际需求对图像进行进一步处理或保存 - // ... - - // 输出处理后的图像信息 - std::cout << "Image white balanced: " << imagePath << std::endl; -} - -void ImageProcessingPlugin::Resize(const json ¶ms) -{ - std::string imagePath = params[0].get(); - int width = params[1].get(); - int height = params[2].get(); - - // 检查缓存中是否存在图像 - if (imageCache.find(imagePath) != imageCache.end()) - { - // 从缓存中获取图像并进行大小调整 - CImg image = imageCache[imagePath]; - image.resize(width, height); - imageCache[imagePath] = image; - } - else - { - // 加载图像,并进行大小调整 - CImg image(imagePath.c_str()); - image.resize(width, height); - imageCache[imagePath] = image; - } - - // 在这里可以根据实际需求对图像进行进一步处理或保存 - // ... - - // 输出处理后的图像信息 - std::cout << "Image resized: " << imagePath << std::endl; -} - -void ImageProcessingPlugin::ImageToBase64(const json ¶ms) -{ - // 读取图片 - try - { - std::string imagePath = params["path"].get(); - - CImg image(imagePath.c_str()); - - // 将图片数据转为字符串 - std::ostringstream oss; - //image.save(oss, "jpg"); - std::string imageData = oss.str(); - - // 将图片数据进行Base64编码 - std::string base64Data; - const std::string base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - int currentIndex = 0; - int padding = 0; - unsigned int value = 0; - for (char c : imageData) - { - value = (value << 8) | c; - padding += 8; - - while (padding >= 6) - { - padding -= 6; - unsigned int index = (value >> padding) & 0x3F; - base64Data += base64Chars[index]; - } - } - - // 处理剩余的位数 - if (padding > 0) - { - value <<= 6 - padding; - unsigned int index = value & 0x3F; - base64Data += base64Chars[index]; - padding += 2; - while (padding < 8) - { - base64Data += '='; - padding += 2; - } - } - } - catch (const json::parse_error &e) - { - std::cerr << e.what() << '\n'; - } - catch (const std::exception &e) - { - std::cerr << e.what() << '\n'; - } -} - -void ImageProcessingPlugin::Base64ToImage(const json ¶ms) -{ - // 将Base64解码为图片数据 - std::string base64Data = params["base64"].get(); - std::string outputPath = params["path"].get(); - std::string imageData; - const std::string base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - int currentIndex = 0; - unsigned int value = 0; - int padding = 0; - for (char c : base64Data) - { - if (c == '=') - { - padding++; - continue; - } - value = (value << 6) | base64Chars.find(c); - padding--; - if (padding == 0) - { - imageData += (value >> 16) & 0xFF; - imageData += (value >> 8) & 0xFF; - imageData += value & 0xFF; - value = 0; - } - } - - // 创建CImg对象并保存为图片文件 - std::istringstream iss(imageData); - CImg image; - //image.load(iss); - - image.save(outputPath.c_str()); -} - -double ImageProcessingPlugin::calc_hfd(const CImg &img, int outer_diameter) -{ - if (outer_diameter == 0) - { - outer_diameter = 60; - } - - // 计算图像的均值 - double mean = img.mean(); - - // 对图像进行处理 - CImg output = img; - cimg_forXY(img, x, y) - { - if (img(x, y) < mean) - { - output(x, y) = 0; - } - else - { - output(x, y) -= static_cast(mean); - } - } - - // 计算外圆直径 - double out_radius = static_cast(outer_diameter) / 2; - - // 计算中心点坐标 - int center_x = img.width() / 2; - int center_y = img.height() / 2; - - // 计算 HFD - double _sum = 0, sum_dist = 0; - cimg_forXY(img, x, y) - { - if (pow(x - center_x, 2) + pow(y - center_y, 2) <= pow(out_radius, 2)) - { - _sum += (output(x, y) != 0); - sum_dist += output(x, y) * sqrt(pow(x - center_x, 2) + pow(y - center_y, 2)); - } - } - - if (_sum != 0) - { - return 2 * sum_dist / _sum; - } - else - { - return sqrt(2) * out_radius; - } -} - -void ImageProcessingPlugin::calc_dark_noise(const CImg &dark_img, float &average_dark, float &sigma_dark, float &sigma_readout) -{ - // 计算均值 - average_dark = 0; - cimg_forXY(dark_img, x, y) - { - average_dark += dark_img(x, y); - } - average_dark /= (dark_img.width() * dark_img.height()); - - // 计算标准差 - sigma_dark = 0; - sigma_readout = 0; - cimg_forXY(dark_img, x, y) - { - sigma_dark += pow(dark_img(x, y) - average_dark, 2); - if (x < dark_img.width() - 1) - { - sigma_readout += pow(dark_img(x, y) - dark_img(x + 1, y), 2); - } - if (y < dark_img.height() - 1) - { - sigma_readout += pow(dark_img(x, y) - dark_img(x, y + 1), 2); - } - } - sigma_dark = sqrt(sigma_dark / (dark_img.width() * dark_img.height())); - sigma_readout = sqrt(sigma_readout / (2 * (dark_img.width() - 1) * dark_img.height() + 2 * dark_img.width() * (dark_img.height() - 1))); -} - -void ImageProcessingPlugin::detect_stars(const char *filename, int threshold, int max_radius) -{ - // 读取图像并记录日志 - DLOG_F(INFO, "Loading image: {}", filename); - CImg img(filename); - - // 转换成灰度图像 - CImg gray = img.get_RGBtoYCbCr().get_channel(0); - - // 阈值处理 - CImg binary = gray.threshold(threshold); - - // 星点检测 - CImg stars(binary.width(), binary.height(), 1, 1, 0); - int count = 0; - cimg_forXY(binary, x, y) - { - if (binary(x, y) == 0) - { - for (int r = 1; r <= max_radius; r++) - { - bool is_star = true; - for (int t = 0; t < stars.spectrum(); t++) - { - int tx = x + r * std::cos(t * cimg::PI / 4); - int ty = y + r * std::sin(t * cimg::PI / 4); - if (tx < 0 || tx >= binary.width() || ty < 0 || ty >= binary.height() || binary(tx, ty) != 0) - { - is_star = false; - break; - } - } - if (is_star) - { - const unsigned char red[] = {255, 0, 0}; - stars.draw_circle(x, y, r, red, 1); - count++; - } - } - } - } - DLOG_F(INFO, "Finished detecting {} stars in image: {}", count, filename); -} - -void ImageProcessingPlugin::compress_image(CImg &img, int compress_ratio) -{ - // 计算压缩后的图像宽度和高度,并创建新图像 - int new_width = img.width() / compress_ratio, new_height = img.height() / compress_ratio; - CImg new_img(new_width, new_height, 1, img.spectrum()); - - // 对每个新像素点,计算与原图像对应的多个像素的平均值,并输出调试日志 - DLOG_F(INFO, "Compress the image with ratio {}.", compress_ratio); - cimg_forXY(new_img, x, y) - { - int sum_r = 0, sum_g = 0, sum_b = 0, count = 0; - for (int i = 0; i < compress_ratio; i++) - { - for (int j = 0; j < compress_ratio; j++) - { - int px = x * compress_ratio + i, py = y * compress_ratio + j; - if (px < img.width() && py < img.height()) - { - sum_r += img(px, py, 0, 0); - if (img.spectrum() > 1) - { - sum_g += img(px, py, 0, 1); - sum_b += img(px, py, 0, 2); - } - count++; - } - } - } - new_img(x, y, 0, 0) = sum_r / count; - if (img.spectrum() > 1) - { - new_img(x, y, 0, 1) = sum_g / count; - new_img(x, y, 0, 2) = sum_b / count; - } - } - - img = new_img; // 将压缩后的图像覆盖原始图像 -} - -CImg ImageProcessingPlugin::read_image(const char *filename) -{ - CImg img; - // 检查文件是否存在 - if (std::ifstream(filename)) - { - // 读取图片 - try - { - img.load(filename); - } - catch (CImgIOException &e) - { - LOG_F(ERROR, "Error reading image file: {}", e.what()); - } - } - else - { - LOG_F(ERROR, "Error: image file does not exist"); - } - return img; -} - -bool ImageProcessingPlugin::read_color_image(const char *filename, CImg &image) -{ - image.load(filename); - if (image.spectrum() == 1) - { - return false; - } - return true; -} - -bool convert_fits_to_cimg(fitsfile *fits_image, CImg &cimg_image) -{ - long fpixel[3] = {1, 1, 1}; - int status = 0; - long nelements = cimg_image.width() * cimg_image.height(); - unsigned char *buffer = new unsigned char[nelements]; - fits_read_pix(fits_image, TBYTE, fpixel, nelements, nullptr, buffer, nullptr, &status); - - if (status != 0) - { - LOG_F(ERROR, "Failed to read FITS image data: {}", status); - delete[] buffer; - return false; - } - - // 将数据从buffer中拷贝到CImg对象中 - cimg_forXY(cimg_image, x, y) - { - cimg_image(x, y) = buffer[y * cimg_image.width() + x]; - } - - delete[] buffer; - return true; -} - -bool ImageProcessingPlugin::read_fits_image(const char *filename, CImg &image) -{ - fitsfile *fits_image; - int status = 0; - fits_open_file(&fits_image, filename, READONLY, &status); - if (status != 0) - { - LOG_F(ERROR, "Failed to open FITS file {}: {}", filename, status); - return false; - } - - // 获取图像大小和通道数 - int naxis; - long naxes[3]; - fits_get_img_dim(fits_image, &naxis, &status); - fits_get_img_size(fits_image, 3, naxes, &status); - - if (naxis == 3 && naxes[2] != 1) - { - fits_close_file(fits_image, &status); - return false; - } - - // 转化为CImg格式 - image.assign(naxes[0], naxes[1], 1, 1); - // convert_fits_to_cimg(fits_image, image); - - fits_close_file(fits_image, &status); - return true; -} - -bool ImageProcessingPlugin::save_image(const CImg &image, const char *filename) -{ - try - { - image.save(filename); - return true; - } - catch (CImgIOException &) - { - return false; - } -} - -void ImageProcessingPlugin::overlay_image(CImg &img1, const CImg &img2) -{ - int width = std::min(img1.width(), img2.width()); - int height = std::min(img1.height(), img2.height()); - - cimg_forXY(img1, x, y) - { - if (x < width && y < height) - { - img1(x, y) = static_cast(std::round((static_cast(img1(x, y)) + static_cast(img2(x, y))) / 2)); - } - } -} - -void ImageProcessingPlugin::computeHistogram(const char *filename, int *hist, int nbins) -{ - // 加载图像 - CImg img(filename); - - // 清空直方图 - std::memset(hist, 0, sizeof(int) * nbins); - - // 计算直方图 - cimg_for(img, ptr, unsigned char) - { - hist[*ptr]++; - } -} - -// 显示直方图 -void ImageProcessingPlugin::displayHistogram(const int *hist, int nbins) -{ - const int hist_w = 512, hist_h = 400; - CImg hist_img(hist_w, hist_h, 1, 3, 255); - const int hist_max = *std::max_element(hist, hist + nbins); - for (int i = 0; i < nbins; i++) - { - const int x0 = i * hist_w / nbins, x1 = (i + 1) * hist_w / nbins; - const int y = hist[i] * (hist_h - 1) / hist_max; - } - hist_img.display(); -} \ No newline at end of file diff --git a/modules/lithium_image/cimg/image.hpp b/modules/lithium_image/cimg/image.hpp deleted file mode 100644 index 30c42afa..00000000 --- a/modules/lithium_image/cimg/image.hpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * image.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-12-1 - -Description: Image processing plugin for Lithium - -**************************************************/ - -#pragma once - -#include "core/plugin/plugin.hpp" -#include "cimg/CImg.h" -#include - -using namespace cimg_library; - -class ImageProcessingPlugin : public Plugin -{ -public: - ImageProcessingPlugin(const std::string &path, const std::string &version, const std::string &author, const std::string &description); - - void Blur(const json ¶ms); - - void Rotate(const json ¶ms); - - void Crop(const json ¶ms); - - void Sharpen(const json ¶ms); - - void WhiteBalance(const json ¶ms); - - void Resize(const json ¶ms); - - void ImageToBase64(const json ¶ms); - - void Base64ToImage(const json ¶ms); - -private: - double calc_hfd(const CImg &img, int outer_diameter); - - void calc_dark_noise(const CImg &dark_img, float &average_dark, float &sigma_dark, float &sigma_readout); - - void detect_stars(const char *filename, int threshold, int max_radius); - - void compress_image(CImg &img, int compress_ratio); - CImg read_image(const char *filename); - - bool read_color_image(const char *filename, CImg &image); - - bool convert_fits_to_cimg(fitsfile *fits_image, CImg &cimg_image); - - bool read_fits_image(const char *filename, CImg &image); - - bool save_image(const CImg &image, const char *filename); - - void overlay_image(CImg &img1, const CImg &img2); - - void computeHistogram(const char *filename, int *hist, int nbins); - - void displayHistogram(const int *hist, int nbins); - -private: - mutable std::unordered_map> imageCache; -}; \ No newline at end of file diff --git a/modules/lithium_image/config.h.in b/modules/lithium_image/config.h.in deleted file mode 100644 index 75288ee6..00000000 --- a/modules/lithium_image/config.h.in +++ /dev/null @@ -1,3 +0,0 @@ -#cmakedefine01 ENABLE_OPENCV -#cmakedefine01 ENABLE_CIMG -#cmakedefine01 ENABLE_STB \ No newline at end of file diff --git a/modules/lithium_image/config.json b/modules/lithium_image/config.json deleted file mode 100644 index 046aca97..00000000 --- a/modules/lithium_image/config.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Image Processing Module for Lithium", - "description": "This module provides image processing functionality for Lithium.", - "version": "1.0.0", - "author": "Max Qian", - "license": "GPLv3 -or- later", - "dependencies": { - "lithium-plugin": "1.0.0", - "atom-core": "1.0.0" - }, - "main": "lithium_image", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/ElementAstro/Lithium/modules/lithium_image" - }, - "keywords": ["lithium", "image", "processing"] -} diff --git a/modules/lithium_image/main.cpp b/modules/lithium_image/main.cpp deleted file mode 100644 index a7f0f243..00000000 --- a/modules/lithium_image/main.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * lithium_image_main.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-12-1 - -Description: Image processing plugin main - -**************************************************/ - -#include "config.h" - -#if ENABLE_CIMG -#include "cimg/image.hpp" -#endif -#if ENABLE_OPENCV -#include "opencv/image.hpp" -#endif -#include - -std::shared_ptr GetInstance() -{ - return std::make_shared("lithium_image", "1.0.0", "Max Qian", "Image processing plugin"); -} - -json GetInfo() -{ - json config; - config["name"] = "lithium_image"; - config["version"] = "1.0.0"; - config["description"] = "Image processing plugin"; - config["author"] = "Max Qian"; - config["email"] = "astro_air@126.com"; - config["url"] = "lightapt.com"; - config["license"] = "GPLv3"; - config["copyright"] = "2023 Max Qian. All rights reserved"; - return config; -} \ No newline at end of file diff --git a/modules/lithium_image/opencv/image.cpp b/modules/lithium_image/opencv/image.cpp deleted file mode 100644 index 138808d0..00000000 --- a/modules/lithium_image/opencv/image.cpp +++ /dev/null @@ -1,482 +0,0 @@ -#include "cvimage.hpp" - -#include -#include -#include - -#include - -using namespace cv; - -void CalHist(Mat img, std::vector &bgr_planes, std::vector &hist) -{ - int histSize = 65535; - float range[] = {0, 65535}; - const float *histRange = {range}; - bool accumulate = false; - - split(img, bgr_planes); - - for (int i = 0; i < 3; i++) - { - calcHist(&bgr_planes[i], 1, 0, Mat(), hist[i], 1, &histSize, &histRange, accumulate); - } -} - -Mat CalGrayHist(Mat img) -{ - int histSize = 65535; - float range[] = {0, 65535}; - const float *histRange = {range}; - bool accumulate = false; - - Mat gray_hist; - calcHist(&img, 1, 0, Mat(), gray_hist, 1, &histSize, &histRange, accumulate); - - return gray_hist; -} - -Mat Stretch_WhiteBalance(std::vector hists, std::vector bgr_planes) -{ - std::vector planes; - std::vector highs(3); - double max_para = 0.0001; - double min_para = 0.0001; - - for (int i = 0; i < 3; i++) - { - Mat hist = hists[i]; - Mat plane = bgr_planes[i]; - - Mat nonzero_indices; - findNonZero(hist, nonzero_indices); - - int nonezero_len = nonzero_indices.rows; - int min_index = nonezero_len * min_para; - int max_index = nonezero_len * (1 - max_para) - 1; - double min = nonzero_indices.at(min_index).x; - double max = nonzero_indices.at(max_index).x; - - plane.convertTo(plane, CV_32S); - plane = (plane - min) / (max - min) * 65535; - threshold(plane, plane, 65535, 65535, THRESH_TRUNC); - plane.convertTo(plane, CV_16U); - - planes.push_back(plane); - highs[i] = (hist.rows - min) / (max - min) * 65535; - } - - double high_mean = std::accumulate(highs.begin(), highs.end(), 0.0) / highs.size(); - - Mat planes_b = planes[0] * (high_mean / highs[0]); - Mat planes_g = planes[1] * (high_mean / highs[1]); - Mat planes_r = planes[2] * (high_mean / highs[2]); - - threshold(planes_b, planes_b, 65535, 65535, THRESH_TRUNC); - threshold(planes_g, planes_g, 65535, 65535, THRESH_TRUNC); - threshold(planes_r, planes_r, 65535, 65535, THRESH_TRUNC); - planes_b.convertTo(planes_b, CV_16U); - planes_g.convertTo(planes_g, CV_16U); - planes_r.convertTo(planes_r, CV_16U); - - Mat dst; - merge(std::vector{planes_b, planes_g, planes_r}, dst); - - return dst; -} -Mat StretchGray(Mat hist, Mat plane) -{ - double max_para = 0.01; - double min_para = 0.01; - - Mat nonzero_indices; - findNonZero(hist, nonzero_indices); - - int nonezero_len = nonzero_indices.rows; - int min_index = nonezero_len * min_para; - int max_index = nonezero_len * (1 - max_para) - 1; - double min = nonzero_indices.at(min_index).x; - double max = nonzero_indices.at(max_index).x; - - threshold(plane, plane, max, 65535, THRESH_TRUNC); - threshold(plane, plane, min, 0, THRESH_TOZERO); - - plane.convertTo(plane, CV_16U); - - Mat stretch_plane; - plane.convertTo(stretch_plane, CV_32F); - - // 计算中值(使用排序方式) - Mat sorted_plane; - sort(stretch_plane.reshape(1), sorted_plane, SORT_ASCENDING); - double gradMed = sorted_plane.at(sorted_plane.rows / 2); - - double Mt = gradMed / 30000; - pow(stretch_plane / 65535, 1 / Mt, stretch_plane); - threshold(stretch_plane, stretch_plane, 65535, 65535, THRESH_TRUNC); - stretch_plane.convertTo(stretch_plane, CV_16U); - - return stretch_plane; -} - -Mat DebayerStarCount(Mat img, int Thres) -{ - Mat gray; - if (img.channels() == 3) - { - cvtColor(img, gray, COLOR_BGR2GRAY); - } - else - { - gray = img; - } - - Mat blur; - GaussianBlur(gray, blur, Size(5, 5), 0); - - Mat thresh; - threshold(blur, thresh, Thres, 255, THRESH_BINARY); - - std::vector> contours; - std::vector hierarchy; - findContours(thresh, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); - - for (size_t i = 0; i < contours.size(); i++) - { - double area = contourArea(contours[i]); - if (area >= 10) - { - Point2f center; - float radius; - minEnclosingCircle(contours[i], center, radius); - int x = static_cast(center.x); - int y = static_cast(center.y); - circle(img, center, static_cast(radius), Scalar(0, 0, 255), 2); - } - } - - return img; -} - -bool insideCircle(int x, int y, int centerX, int centerY, float radius) -{ - return sqrt(pow(x - centerX, 2) + pow(y - centerY, 2)) < radius; -} - -double calcHfd(Mat inImage, int inOuterDiameter) -{ - Mat Img = inImage - mean(inImage)[0]; - Img = max(Img, 0); - - int width = Img.cols; - int height = Img.rows; - double outerRadius = inOuterDiameter / 2.0; - - double sum = 0; - double sumDist = 0; - int centerX = ceil(width / 2.0); - int centerY = ceil(height / 2.0); - - for (int x = 0; x < width; x++) - { - for (int y = 0; y < height; y++) - { - if (insideCircle(x, y, centerX, centerY, outerRadius)) - { - sum += Img.at(y, x); - sumDist += Img.at(y, x) * sqrt(pow(x - centerX, 2) + pow(y - centerY, 2.0)); - } - } - } - - sum = sum; - double hfd; - if (sum > 0) - { - hfd = 2.0 * sumDist / sum; - } - else - { - hfd = sqrt(2.0) * outerRadius; - } - - return hfd; -} - -Mat DebayerStarCountHfr(Mat img, bool do_star_mark = false) -{ - int histSize = 255; - - Mat gray; - if (img.channels() == 3) - { - cvtColor(img, gray, COLOR_BGR2GRAY); - } - else - { - gray = img; - cvtColor(img, img, COLOR_GRAY2BGR); - } - - Mat gray_hist; - calcHist(&gray, 1, 0, Mat(), gray_hist, 1, &histSize, 0, true); - - Mat blur; - medianBlur(gray, blur, 1); - - Mat nonzero_indices; - findNonZero(gray_hist, nonzero_indices); - - int nonezero_len = nonzero_indices.rows; - int thres = static_cast(nonzero_indices.at(nonezero_len * 0.5).x); - - Mat thresh; - threshold(blur, thresh, thres, 255, THRESH_BINARY); - - std::vector> contours; - std::vector hierarchy; - findContours(thresh, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); - - int StarCount = 0; - std::vector HfrList; - for (size_t i = 0; i < contours.size(); i++) - { - double area = contourArea(contours[i]); - if (area >= 3) - { - StarCount++; - Point2f center; - float radius; - minEnclosingCircle(contours[i], center, radius); - int x = static_cast(center.x); - int y = static_cast(center.y); - Mat roi = gray(Rect(x - static_cast(radius) - 5, y - static_cast(radius) - 5, - static_cast(radius) * 2 + 10, static_cast(radius) * 2 + 10)); - double hfr = calcHfd(roi, 60) / 2; - HfrList.push_back(hfr); - if (do_star_mark) - { - circle(img, center, static_cast(radius) + 10, Scalar(0, 255, 0), 1); - putText(img, std::to_string(round(hfr * 100) / 100), center, FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 255, 0), 1, LINE_AA); - } - } - } - - double AvgHfr = (HfrList.empty()) ? 0 : std::accumulate(HfrList.begin(), HfrList.end(), 0.0) / HfrList.size(); - - return img; -} - -int main() -{ - std::string filepath = "path_to_image"; - int Thres = 128; - - Mat img = imread(filepath); - - // Example usage of the functions - std::vector bgr_planes(3); - std::vector hist(3); - CalHist(img, bgr_planes, hist); - - Mat gray_hist = CalGrayHist(img); - - Mat dst = Stretch_WhiteBalance(hist, bgr_planes); - - Mat result = DebayerStarCount(img, Thres); - - Mat result_hfr = DebayerStarCountHfr(img, true); - - return 0; -} - -Mat Cal_MTF(int m, const Mat &img) -{ - Mat mtf_img = img.clone(); - Mat zeros = (img == 0); - Mat halfs = (img == m); - Mat ones = (img == 1); - Mat others = ~(zeros | halfs | ones); - mtf_img.setTo((m - 1) * mtf_img / ((((2 * m) - 1) * mtf_img) - m), others); - return mtf_img; -} - -Mat GrayStretch(const Mat &img) -{ - double black_clip = -1.25; - double target_bkg = 0.1; - Mat norm_img; - normalize(img, norm_img, 0, 1, NORM_MINMAX); - double median = median(norm_img)[0]; - double avgbias = 0.0; - - int size = norm_img.rows * norm_img.cols; - for (int i = 0; i < norm_img.rows; i++) - { - for (int j = 0; j < norm_img.cols; j++) - { - avgbias += std::abs(norm_img.at(i, j) - median); - } - } - avgbias /= size; - - double c0 = std::min(std::max(median + (black_clip * avgbias), 0.0), 1.0); - double m = (median - c0) * (target_bkg - 1) / ((((2 * target_bkg) - 1) * (median - c0)) - target_bkg); - double c1 = 1.0; - - for (int i = 0; i < norm_img.rows; i++) - { - for (int j = 0; j < norm_img.cols; j++) - { - if (norm_img.at(i, j) < c0) - { - norm_img.at(i, j) = 0; - } - else - { - norm_img.at(i, j) = (norm_img.at(i, j) - c0) / (1 - c0); - norm_img.at(i, j) = MTF(m, norm_img.at(i, j)); - } - } - } - - Mat dst_img; - normalize(norm_img, dst_img, 0, 65535, NORM_MINMAX); - dst_img.convertTo(dst_img, CV_16U); - return dst_img; -} - -double CalScale(const Mat &img, int resize_size = 1080) -{ - int width = img.cols; - int height = img.rows; - double scale; - - if (width > height) - { - scale = resize_size / static_cast(width); - } - else - { - scale = resize_size / static_cast(height); - } - - return scale; -} - -double Cal_Middev(double mid, const Mat &img) -{ - Mat mid_dev; - absdiff(img, mid, mid_dev); - double median_deviation = median(mid_dev)[0]; - return median_deviation; -} - -Mat AutoParamCompute_OneChannel(const Mat &img) -{ - double maxNum = 255.0; - Mat norm_img; - normalize(img, norm_img, 0, 1, NORM_MINMAX); - double median = median(norm_img)[0]; - - double med_dev = Cal_Middev(median * maxNum, img); - double MADN = 1.4826 * (med_dev / maxNum); - - double B = 0.25; - double C = -2.8; - bool upper_half = (median / maxNum) > 0.5; - double shadows = 0.0; - double highlights = 1.0; - - if (upper_half && MADN != 0) - { - shadows = std::min(1.0, std::max(0.0, (median / maxNum) + C * MADN)); - } - else if (!upper_half && MADN != 0) - { - highlights = std::min(1.0, std::max(0.0, (median / maxNum) - C * MADN)); - } - - double X, M, midtones; - if (!upper_half) - { - X = (median / maxNum) - shadows; - M = B; - } - else - { - X = B; - M = highlights - (median / maxNum); - } - - if (X == 0) - { - midtones = 0.0; - } - else if (X == M) - { - midtones = 0.5; - } - else if (X == 1) - { - midtones = 1.0; - } - else - { - midtones = ((M - 1) * X) / (((2 * M - 1) * X) - M); - } - - return norm_img, shadows, midtones, highlights; -} - -Mat Stretch_OneChannel(const Mat &norm_img, double shadows, double midtones, double highlights) -{ - double hsRangeFactor = (highlights == shadows) ? 1.0 : 1.0 / (highlights - shadows); - double k1 = (midtones - 1) * hsRangeFactor; - double k2 = ((2 * midtones) - 1) * hsRangeFactor; - - Mat stretched_img = norm_img.clone(); - Mat downshadow = (norm_img < shadows); - stretched_img.setTo(0, downshadow); - - Mat uphighlight = (norm_img > highlights); - stretched_img.setTo(1, uphighlight); - - Mat others = ~(downshadow | uphighlight); - stretched_img.setTo((stretched_img - shadows) * k1 / (((stretched_img - shadows) * k2) - midtones), others); - - return stretched_img; -} - -Mat ComputeStretch_ThreeChannels(const Mat &img, bool do_jpg) -{ - std::vector bgr_planes; - split(img, bgr_planes); - - Mat dst_img(img.rows, img.cols, CV_8UC3); - Mat norm_img, shadows, midtones, highlights; - - std::tie(norm_img, shadows, midtones, highlights) = AutoParamCompute_OneChannel(bgr_planes[0]); - Mat plane0 = Stretch_OneChannel(norm_img, shadows, midtones, highlights); - - std::tie(norm_img, shadows, midtones, highlights) = AutoParamCompute_OneChannel(bgr_planes[1]); - Mat plane1 = Stretch_OneChannel(norm_img, shadows, midtones, highlights); - - std::tie(norm_img, shadows, midtones, highlights) = AutoParamCompute_OneChannel(bgr_planes[2]); - Mat plane2 = Stretch_OneChannel(norm_img, shadows, midtones, highlights); - - if (do_jpg) - { - plane0.convertTo(dst_img.col(0), CV_8U, 255); - plane1.convertTo(dst_img.col(1), CV_8U, 255); - plane2.convertTo(dst_img.col(2), CV_8U, 255); - } - else - { - plane0.convertTo(dst_img.col(0), CV_16U, 65535); - plane1.convertTo(dst_img.col(1), CV_16U, 65535); - plane2.convertTo(dst_img.col(2), CV_16U, 65535); - } - - return dst_img; -} diff --git a/modules/lithium_image/opencv/image.hpp b/modules/lithium_image/opencv/image.hpp deleted file mode 100644 index 0bf9f9d7..00000000 --- a/modules/lithium_image/opencv/image.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -using namespace cv; - -void CalHist(Mat img, std::vector &bgr_planes, std::vector &hist); -Mat CalGrayHist(Mat img); -Mat Stretch_WhiteBalance(std::vector hists, std::vector bgr_planes); -Mat StretchGray(Mat hist, Mat plane); -Mat DebayerStarCount(Mat img, int Thres); -bool insideCircle(int x, int y, int centerX, int centerY, float radius); -double calcHfd(Mat inImage, int inOuterDiameter); -Mat DebayerStarCountHfr(Mat img, bool do_star_mark); -Mat Cal_MTF(int m, const Mat &img); -Mat GrayStretch(const Mat &img); -double CalScale(const Mat &img, int resize_size = 1080); -double Cal_Middev(double mid, const Mat &img); -Mat AutoParamCompute_OneChannel(const Mat &img); -Mat Stretch_OneChannel(const Mat &norm_img, double shadows, double midtones, double highlights); -Mat ComputeStretch_ThreeChannels(const Mat &img, bool do_jpg); \ No newline at end of file diff --git a/modules/lithium_image/opencv/pimage.cpp b/modules/lithium_image/opencv/pimage.cpp deleted file mode 100644 index 4d0912f0..00000000 --- a/modules/lithium_image/opencv/pimage.cpp +++ /dev/null @@ -1,837 +0,0 @@ -/* - * image.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-4-6 - -Description: Image Processing - -**************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -#include "cimg/CImg.h" - -#include "image.hpp" - -using namespace cimg_library; - -namespace Lithium::Image -{ - - CImg read_image(const char *filename) - { - CImg img; - // 检查文件是否存在 - if (std::ifstream(filename)) - { - // 读取图片 - try - { - img.load(filename); - } - catch (CImgIOException &e) - { - spdlog::error("Error reading image file: {}", e.what()); - } - } - else - { - spdlog::error("Error: image file does not exist"); - } - return img; - } - - /** - * @brief 读取彩色图像,如果是灰度图像则不进行处理并返回 false。 - * - * @param filename 图像文件名。 - * @param image 输出参数,用于存储读入的图像。 - * @return true 读入成功且为彩色图像;false 读入成功但为灰度图像。 - */ - bool read_color_image(const char *filename, CImg &image) - { - image.load(filename); - if (image.spectrum() == 1) - { - return false; - } - return true; - } - - /** - * @brief 将FITS格式图像转化为 CImg 格式图像。 - * - * @param fits_image 要转化的FITS格式图像。 - * @param cimg_image 输出参数,用于存储转化后的CImg格式图像。 - * @return true 转化成功;false 转化失败。 - */ - bool convert_fits_to_cimg(fitsfile *fits_image, CImg &cimg_image) - { - long fpixel[3] = {1, 1, 1}; - int status = 0; - long nelements = cimg_image.width() * cimg_image.height(); - unsigned char *buffer = new unsigned char[nelements]; - fits_read_pix(fits_image, TBYTE, fpixel, nelements, nullptr, buffer, nullptr, &status); - - if (status != 0) - { - spdlog::error("Failed to read FITS image data: {}", status); - delete[] buffer; - return false; - } - - // 将数据从buffer中拷贝到CImg对象中 - cimg_forXY(cimg_image, x, y) - { - cimg_image(x, y) = buffer[y * cimg_image.width() + x]; - } - - delete[] buffer; - return true; - } - - /** - * @brief 读取FITS格式图像,如果读入的图像是彩色图像则返回 false。 - * - * @param filename 图像文件名。 - * @param image 输出参数,用于存储读入的图像。 - * @return true 读入成功且为灰度图像;false 读入成功但为彩色图像。 - */ - bool read_fits_image(const char *filename, CImg &image) - { - fitsfile *fits_image; - int status = 0; - fits_open_file(&fits_image, filename, READONLY, &status); - if (status != 0) - { - spdlog::error("Failed to open FITS file {}: {}", filename, status); - return false; - } - - // 获取图像大小和通道数 - int naxis; - long naxes[3]; - fits_get_img_dim(fits_image, &naxis, &status); - fits_get_img_size(fits_image, 3, naxes, &status); - - if (naxis == 3 && naxes[2] != 1) - { - fits_close_file(fits_image, &status); - return false; - } - - // 转化为CImg格式 - image.assign(naxes[0], naxes[1], 1, 1); - convert_fits_to_cimg(fits_image, image); - - fits_close_file(fits_image, &status); - return true; - } - - /** - * @brief 保存图像到文件。 - * - * @param image 要保存的图像。 - * @param filename 文件名。 - * @return true 保存成功;false 保存失败。 - */ - bool save_image(const CImg &image, const char *filename) - { - try - { - image.save(filename); - return true; - } - catch (CImgIOException &) - { - return false; - } - } - - /** - * @brief 裁剪图像 - * - * @param img 输入图像 - * @param x 裁剪区域左上角x坐标 - * @param y 裁剪区域左上角y坐标 - * @param w 裁剪区域宽度 - * @param h 裁剪区域高度 - */ - void crop_image(CImg &img, int x, int y, int w, int h) - { - // 调用CImg库提供的crop()函数进行图像裁剪,并输出调试日志 - DLOG_F(INFO,"Crop the image to ({}, {}), width = {}, height = {}.", x, y, w, h); - img.crop(x, y, x + w - 1, y + h - 1); - } - - /** - * @brief 旋转图像 - * - * @param img 输入图像 - * @param angle 旋转角度(单位:度) - */ - void rotate_image(CImg &img, float angle) - { - // 调用CImg库提供的rotate()函数进行图像旋转,并输出调试日志 - DLOG_F(INFO,"Rotate the image by {} degrees.", angle); - img.rotate(angle); - } - - /** - * @brief 图像翻转函数 - * - * @param img CImg类的引用,表示要翻转的图像 - * @param direction 整数类型,表示翻转方向。0表示水平翻转,1表示垂直翻转 - */ - void flip(CImg &img, int direction) - { - // 添加spdlog的debug输出 - DLOG_F(INFO,"Flipping image..."); - - if (direction == 0) - { // 水平翻转 - cimg_forXY(img, x, y) - { - const int new_x = img.width() - 1 - x; - std::swap(img(x, y, 0), img(new_x, y, 0)); - } - } - else if (direction == 1) - { // 垂直翻转 - cimg_forXY(img, x, y) - { - const int new_y = img.height() - 1 - y; - std::swap(img(x, y, 0), img(x, new_y, 0)); - } - } - - // 添加spdlog的debug输出 - DLOG_F(INFO,"Image flipped."); - } - - /** - * @brief 计算图像直方图 - * - * @param img 输入图像 - * @return std::vector 图像直方图,存储在vector中 - */ - std::vector compute_histogram(CImg &img) - { - // 使用vector存储图像直方图,并输出调试日志 - DLOG_F(INFO,"Compute the histogram of the image."); - std::vector hist(256, 0); - cimg_forXY(img, x, y) - { - int val = (int)img(x, y, 0, 0); // 取出像素值 - hist[val]++; - } - return hist; - } - - /** - * @brief 压缩图像 - * - * @param img 输入图像 - * @param compress_ratio 压缩比例 - */ - void compress_image(CImg &img, int compress_ratio) - { - // 计算压缩后的图像宽度和高度,并创建新图像 - int new_width = img.width() / compress_ratio, new_height = img.height() / compress_ratio; - CImg new_img(new_width, new_height, 1, img.spectrum()); - - // 对每个新像素点,计算与原图像对应的多个像素的平均值,并输出调试日志 - DLOG_F(INFO,"Compress the image with ratio {}.", compress_ratio); - cimg_forXY(new_img, x, y) - { - int sum_r = 0, sum_g = 0, sum_b = 0, count = 0; - for (int i = 0; i < compress_ratio; i++) - { - for (int j = 0; j < compress_ratio; j++) - { - int px = x * compress_ratio + i, py = y * compress_ratio + j; - if (px < img.width() && py < img.height()) - { - sum_r += img(px, py, 0, 0); - if (img.spectrum() > 1) - { - sum_g += img(px, py, 0, 1); - sum_b += img(px, py, 0, 2); - } - count++; - } - } - } - new_img(x, y, 0, 0) = sum_r / count; - if (img.spectrum() > 1) - { - new_img(x, y, 0, 1) = sum_g / count; - new_img(x, y, 0, 2) = sum_b / count; - } - } - - img = new_img; // 将压缩后的图像覆盖原始图像 - } - - /** - * @brief 高斯滤波 - * - * @param image 输入图像 - * @return CImg 经过高斯滤波后的输出图像 - */ - CImg gaussian_filter(const CImg &image) - { - float kernel[9] = {1, 2, 1, 2, 4, 2, 1, 2, 1}; - CImg filter(kernel, 3, 3); - DLOG_F(INFO,"Apply Gaussian filter to the image."); - return image.get_convolve(filter); - } - - /** - * @brief 均值滤波 - * - * @param image 输入图像 - * @return CImg 经过均值滤波后的输出图像 - */ - CImg mean_filter(const CImg &image) - { - float kernel[9] = {1, 1, 1, 1, 1, 1, 1, 1, 1}; - CImg filter(kernel, 3, 3); - DLOG_F(INFO,"Apply mean filter to the image."); - return image.get_convolve(filter); - } - - /** - * @brief 锐化 - * - * @param image 输入图像 - * @return CImg 经过锐化操作后的输出图像 - */ - CImg sharpen(const CImg &image) - { - float kernel[9] = {-1, -1, -1, -1, 9, -1, -1, -1, -1}; - CImg filter(kernel, 3, 3); - DLOG_F(INFO,"Apply sharpen filter to the image."); - return image.get_convolve(filter); - } - - /** - * @brief 对图像进行拉伸 - * - * @param img 原始图像 - * @return CImg 拉伸后的图像 - */ - CImg stretch_image(const CImg &img) - { - return img.get_normalize(0, 255); - } - - /** - * @brief 调整图像的亮度 - * - * @param img 原始图像 - * @param ratio 亮度调整比例 - * @return CImg 调整后的图像 - */ - CImg brighten_image(const CImg &img, float ratio) - { - return img * ratio; - } - - /** - * @brief 检测图像中的星点 - * - * @param filename 图像文件名 - * @param threshold 二值化阈值 - * @param max_radius 最大圆半径 - */ - void detect_stars(const char *filename, int threshold, int max_radius) - { - // 读取图像并记录日志 - DLOG_F(INFO,"Loading image: {}", filename); - CImg img(filename); - - // 转换成灰度图像 - CImg gray = img.get_RGBtoYCbCr().get_channel(0); - - // 阈值处理 - CImg binary = gray.threshold(threshold); - - // 星点检测 - CImg stars(binary.width(), binary.height(), 1, 1, 0); - int count = 0; - cimg_forXY(binary, x, y) - { - if (binary(x, y) == 0) - { - for (int r = 1; r <= max_radius; r++) - { - bool is_star = true; - for (int t = 0; t < stars.spectrum(); t++) - { - int tx = x + r * std::cos(t * cimg::PI / 4); - int ty = y + r * std::sin(t * cimg::PI / 4); - if (tx < 0 || tx >= binary.width() || ty < 0 || ty >= binary.height() || binary(tx, ty) != 0) - { - is_star = false; - break; - } - } - if (is_star) - { - const unsigned char red[] = {255, 0, 0}; - stars.draw_circle(x, y, r, red, 1); - count++; - } - } - } - } - DLOG_F(INFO,"Finished detecting {} stars in image: {}", count, filename); - } - - /** - * @brief 判断图像位数 - * - * @param img 输入图像 - */ - int bit_depth(const CImg &img) - { - int depth = img.depth(); - if (depth == 1) - { - DLOG_F(INFO,"The bit depth of the image is: 1 bit"); - } - else if (depth == 3) - { - DLOG_F(INFO,"The bit depth of the image is: 24 bits"); - } - else - { - DLOG_F(INFO,"The bit depth of the image is: {} bits", depth * 8); - } - return depth; - } - - /** - * @brief 计算图像曝光时间 - * - * @param img 输入图像 - * @param iso ISO感光度 - * @param aperture 光圈值 - * @param shutter_speed 快门速度 - */ - float calc_exposure_time(const CImg &img, const int iso, const float aperture, const float shutter_speed) - { - // 计算光量总量 - float total_light = 0; - cimg_forXY(img, x, y) - { - total_light += img(x, y, 0) + img(x, y, 1) + img(x, y, 2); - } - - // 计算曝光时间 - float exposure_time = 100.0f * iso * aperture * aperture / (shutter_speed * total_light); - DLOG_F(INFO,"The exposure time of the astronomy camera is: {}s", exposure_time); - return exposure_time; - } - - /** - * @brief 计算天文相机曝光时间 - * - * @param gain 增益倍数 - * @param t 曝光时间 - * @param dark_noise 暗电流噪声 - * @param read_noise 读出噪声 - */ - float calc_astro_exposure_time(const float gain, const float t, const float dark_noise, const float read_noise) - { - // 计算曝光时间 - float exposure_time = (gain * t) / (dark_noise * dark_noise - read_noise * read_noise); - DLOG_F(INFO,"The exposure time of the astronomy camera is: {}s", exposure_time); - return exposure_time; - } - - /** - * @brief 计算暗电流噪声和读出噪声 - * - * @param dark_img 暗场图像 - * @param average_dark 均值(暗电流) - * @param sigma_dark 标准差(暗电流) - * @param sigma_readout 标准差(读出噪声) - */ - void calc_dark_noise(const CImg &dark_img, float &average_dark, float &sigma_dark, float &sigma_readout) - { - // 计算均值 - average_dark = 0; - cimg_forXY(dark_img, x, y) - { - average_dark += dark_img(x, y); - } - average_dark /= (dark_img.width() * dark_img.height()); - - // 计算标准差 - sigma_dark = 0; - sigma_readout = 0; - cimg_forXY(dark_img, x, y) - { - sigma_dark += pow(dark_img(x, y) - average_dark, 2); - if (x < dark_img.width() - 1) - { - sigma_readout += pow(dark_img(x, y) - dark_img(x + 1, y), 2); - } - if (y < dark_img.height() - 1) - { - sigma_readout += pow(dark_img(x, y) - dark_img(x, y + 1), 2); - } - } - sigma_dark = sqrt(sigma_dark / (dark_img.width() * dark_img.height())); - sigma_readout = sqrt(sigma_readout / (2 * (dark_img.width() - 1) * dark_img.height() + 2 * dark_img.width() * (dark_img.height() - 1))); - } - - /** - * @brief 计算图像的 HFD - * - * @param img 待计算的图像 - * @param outer_diameter 外径直径 - * @return double 返回 HFD 值 - */ - double calc_hfd(const CImg &img, int outer_diameter) - { - if (outer_diameter == 0) - { - outer_diameter = 60; - } - - // 计算图像的均值 - double mean = img.mean(); - - // 对图像进行处理 - CImg output = img; - cimg_forXY(img, x, y) - { - if (img(x, y) < mean) - { - output(x, y) = 0; - } - else - { - output(x, y) -= static_cast(mean); - } - } - - // 计算外圆直径 - double out_radius = static_cast(outer_diameter) / 2; - - // 计算中心点坐标 - int center_x = img.width() / 2; - int center_y = img.height() / 2; - - // 计算 HFD - double _sum = 0, sum_dist = 0; - cimg_forXY(img, x, y) - { - if (pow(x - center_x, 2) + pow(y - center_y, 2) <= pow(out_radius, 2)) - { - _sum += (output(x, y) != 0); - sum_dist += output(x, y) * sqrt(pow(x - center_x, 2) + pow(y - center_y, 2)); - } - } - - if (_sum != 0) - { - return 2 * sum_dist / _sum; - } - else - { - return sqrt(2) * out_radius; - } - } - - /** - * @brief 计算图像的均值、方差、峰值信噪比等图像质量参数 - * - * @param img 待检测的图像 - * @return std::tuple 返回一个元组,包含图像的均值、方差和峰值信噪比 - */ - std::tuple image_quality_measures(const CImg &img) - { - double mean_value = img.mean(); - double variance = img.variance(); - double max_value = img.max(); - double noise_power = variance / (max_value * max_value); - double peak_signal_noise_ratio = 10 * log10(max_value * max_value / variance); - return std::make_tuple(mean_value, variance, peak_signal_noise_ratio, noise_power); - } - - /** - * @brief 计算图像的均值 - * - * @param img 待计算的图像 - * @return double 返回图像的均值 - */ - double calc_mean(const CImg &img) - { - return img.mean(); - } - - /** - * @brief 对图像进行校准 - * - * @param img 待校准的图像 - * @param threshold 校准阈值 - * @return void - */ - void calibrate_image(CImg &img, double threshold) - { - double mean = calc_mean(img); - - cimg_forXY(img, x, y) - { - if (img(x, y) <= threshold) - { - img(x, y) = 0; - } - else - { - img(x, y) = static_cast(std::round((img(x, y) - threshold) * 255.0 / (mean - threshold))); - } - } - } - - /** - * @brief 计算图像相似度 - * - * @param img1 图像1 - * @param img2 图像2 - * @return double 返回两个图像之间的相似度 - */ - double calc_similarity(const CImg &img1, const CImg &img2) - { - int width = std::min(img1.width(), img2.width()); - int height = std::min(img1.height(), img2.height()); - - double sum_diff = 0; - cimg_forXY(img1, x, y) - { - if (x < width && y < height) - { - sum_diff += std::abs(static_cast(img1(x, y)) - static_cast(img2(x, y))); - } - } - - return 1 - sum_diff / (255.0 * width * height); - } - - /** - * @brief 将图像2叠加到图像1上 - * - * @param img1 图像1 - * @param img2 图像2 - * @return void - */ - void overlay_image(CImg &img1, const CImg &img2) - { - int width = std::min(img1.width(), img2.width()); - int height = std::min(img1.height(), img2.height()); - - cimg_forXY(img1, x, y) - { - if (x < width && y < height) - { - img1(x, y) = static_cast(std::round((static_cast(img1(x, y)) + static_cast(img2(x, y))) / 2)); - } - } - } - - /** - * @brief 给图像添加椒盐噪声 - * - * @param image 原始图像 - * @param threshold 噪声密度 - * @return CImg 添加噪声后的图像 - */ - CImg add_salt_pepper_noise(CImg image, double threshold) - { - CImg output(image.width(), image.height(), 1, image.spectrum(), 0); - double thres = 1 - threshold; - cimg_forXY(image, x, y) - { - double randomnum = ((double)rand() / RAND_MAX); - if (randomnum < threshold) - { - output(x, y) = 0; - } - else if (randomnum > thres) - { - output(x, y) = 255; - } - else - { - output(x, y) = image(x, y); - } - } - return output; - } - - /** - * @brief 给图像添加高斯噪声 - * - * @param image 原始图像 - * @param mean 均值 - * @param var 方差 - * @return CImg 添加噪声后的图像 - */ - CImg add_gaussian_noise(CImg image, double mean, double var) - { - CImg output(image.width(), image.height(), 1, image.spectrum(), 0); - CImg noise(image.width(), image.height(), 1, image.spectrum(), 0); - cimg_forXY(image, x, y) - { - noise(x, y) = ((double)rand() / RAND_MAX) * var + mean; - output(x, y) = image(x, y) + noise(x, y); - } - return output; - } - - /** - * @brief 将彩色图像按照颜色通道拆分为不同的图片。 - * - * @param image 要拆分的彩色图像。 - * @param r 输出参数,用于存储红色通道。 - * @param g 输出参数,用于存储绿色通道。 - * @param b 输出参数,用于存储蓝色通道。 - */ - void split_color_image(const CImg &image, CImg &r, CImg &g, CImg &b) - { - r = image.get_channel(0); - g = image.get_channel(1); - b = image.get_channel(2); - } - -} - -/* -int main() { - CImg img = read_image("test.jpg"); - - // 裁剪图像 - crop_image(img, 100, 100, 200, 200); - - // 旋转图像 - rotate_image(img, 45.0f); - - // 计算直方图 - std::vector hist = compute_histogram(img); - - // 压缩图像 - compress_image(img, 4); - - // 读取图像 - CImg image("example.jpg"); - - // 进行滤波和锐化操作 - CImg result_gaussian = gaussian_filter(image), result_mean = mean_filter(image), result_sharpen = sharpen(image); - - // 显示结果 - CImgDisplay original_disp(image, "Original Image"), gaussian_disp(result_gaussian, "Gaussian Filtered Image"), mean_disp(result_mean, "Mean Filtered Image"), sharpen_disp(result_sharpen, "Sharpened Image"); - while (!original_disp.is_closed() || !gaussian_disp.is_closed() || !mean_disp.is_closed() || !sharpen_disp.is_closed()) { - original_disp.wait(); - } - - // 设置ISO感光度、光圈值和快门速度 - int iso = 100; - float aperture = 2.8f; - float shutter_speed = 1.0f / 250; - - // 调用函数计算曝光时间 - calc_exposure_time(img, iso, aperture, shutter_speed); - - // 计算暗电流噪声和读出噪声 - float average_dark, sigma_dark, sigma_readout; - calc_dark_noise(img, average_dark, sigma_dark, sigma_readout); - - std::cout << "The average dark value is: " << average_dark << std::endl; - std::cout << "The dark noise is: " << sigma_dark << std::endl; - std::cout << "The readout noise is: " << sigma_readout << std::endl; - - double hfd = calc_hfd(img, 60); - std::cout << "HFD: " << hfd << std::endl; - - // 计算图像质量参数 - std::tuple quality_measures = image_quality_measures(img); - double mean_value = std::get<0>(quality_measures); - double variance = std::get<1>(quality_measures); - double peak_signal_noise_ratio = std::get<2>(quality_measures); - - // 显示结果 - std::cout << "Mean value: " << mean_value << std::endl; - std::cout << "Variance: " << variance << std::endl; - std::cout << "Peak signal-to-noise ratio: " << peak_signal_noise_ratio << std::endl; - - CImg img_new; - - // 进行图像校准和比对 - calibrate_image(img, 50); - - while (true) { - // 加载新的图像 - img_new.load("image2.bmp"); - - // 进行图像校准和比对 - calibrate_image(img_new, 50); - - double similarity = calc_similarity(img, img_new); - - if (similarity >= 0.9) { - // 将新图像叠加到原图像上 - overlay_image(img, img_new); - - // 显示叠加后的图像 - CImgDisplay disp(img, "Overlayed image"); - while (!disp.is_closed()) { - disp.wait(); - } - } - else { - std::cout << "Image not matched. Similarity: " << similarity << std::endl; - } - } - - CImg r, g, b; - split_color_image(img, r, g, b); - save_image(r, "red_channel.jpg"); - save_image(g, "green_channel.jpg"); - save_image(b, "blue_channel.jpg"); - - return 0; -} - -*/ diff --git a/modules/lithium_search/CMakeLists.txt b/modules/lithium_search/CMakeLists.txt deleted file mode 100644 index c5617477..00000000 --- a/modules/lithium_search/CMakeLists.txt +++ /dev/null @@ -1,126 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(lithium_search C CXX) - -option(ENABLE_DEBUG "Enable Debug Mode" OFF) -if(ENABLE_DEBUG) - set(ENABLE_DEBUG_FLAG "1") -endif() - -option(ENABLE_SQLITE_FLAG "Enable Sqlite" ON) -if (ENABLE_SQLITE_FLAG) - find_package(SQLite3 REQUIRED) - message("-- Found SQLite3 ${SQLite3_VERSION}: ${SQLite3_LIBRARIES}") - set(ENABLE_SQLITE "1") -else() - message("-- SQLite3 Not Found. Will not build sqlite search module.") -endif() - -option(ENABLE_MYSQL_FLAG "Enable MySQL" OFF) -if (ENABLE_MYSQL_FLAG) - find_package(MySQL REQUIRED) - message("-- Found MySQL ${MySQL_VERSION}: ${MySQL_LIBRARIES}") - set(ENABLE_MYSQL "1") -else() - message("-- MySQL Not Found. Will not build mysql search module.") -endif() - -option(ENABLE_POSTGRESQL_FLAG "Enable PostgreSQL" OFF) -if (ENABLE_POSTGRESQL_FLAG) - find_package(PostgreSQL REQUIRED) - message("-- Found PostgreSQL ${PostgreSQL_VERSION}: ${PostgreSQL_LIBRARIES}") - set(ENABLE_POSTGRESQL "1") -else() - message("-- PostgreSQL Not Found. Will not build postgresql search module.") -endif() - -configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -CHECK_INCLUDE_FILE(format HAS_STD_FORMAT) - -if(NOT HAS_STD_FORMAT) - find_package(fmt REQUIRED) -endif() - -list(APPEND ${PROJECT_NAME}_LIBS - lithiumpluginstatic - loguru - ${CFITSIO_LIBRARIES} -) - -if(ENABLE_SLQLITE_FLAG) - list(APPEND ${PROJECT_NAME}_LIBS - ${SQLite3_LIBRARIES} - ) -endif() - -if(ENABLE_MYSQL_FLAG) - list(APPEND ${PROJECT_NAME}_LIBS - ${MySQL_LIBRARIES} - ) -endif() - -if(ENABLE_POSTGRESQL_FLAG) - list(APPEND ${PROJECT_NAME}_LIBS - ${PostgreSQL_LIBRARIES} - ) -endif() -# Sources -list(APPEND ${PROJECT_NAME}_SOURCES - main.cpp - - ongc/dso.cpp - ongc/ongc.cpp - -) - -# Private Headers -list(APPEND ${PROJECT_NAME}_PRIVATE_HEADERS - -) - -# Build Object Library -add_library(${PROJECT_NAME}_OBJECT OBJECT) -set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) - -target_link_libraries(${PROJECT_NAME}_OBJECT loguru lithiumpluginstatic) - -target_sources(${PROJECT_NAME}_OBJECT - PUBLIC - ${${PROJECT_NAME}_HEADERS} - PRIVATE - ${${PROJECT_NAME}_SOURCES} - ${${PROJECT_NAME}_PRIVATE_HEADERS} -) - -target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) - -add_library(${PROJECT_NAME}static STATIC) - -target_link_libraries(${PROJECT_NAME}static ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -target_link_libraries(${PROJECT_NAME}static ${CMAKE_THREAD_LIBS_INIT}) -target_include_directories(${PROJECT_NAME}static PUBLIC .) - -set_target_properties(${PROJECT_NAME}static PROPERTIES - VERSION ${CMAKE_HYDROGEN_VERSION_STRING} - SOVERSION ${HYDROGEN_SOVERSION} - OUTPUT_NAME ${PROJECT_NAME} # this same name like shared library - backwards compatibility -) - -install(TARGETS ${PROJECT_NAME}static - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) - -add_library(${PROJECT_NAME}shared SHARED) - -target_link_libraries(${PROJECT_NAME}shared ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) -target_link_libraries(${PROJECT_NAME}shared ${CMAKE_THREAD_LIBS_INIT}) -target_include_directories(${PROJECT_NAME}shared PUBLIC .) -set_target_properties(${PROJECT_NAME}shared PROPERTIES - VERSION ${CMAKE_HYDROGEN_VERSION_STRING} - SOVERSION ${HYDROGEN_SOVERSION} - OUTPUT_NAME ${PROJECT_NAME} # this same name like shared library - backwards compatibility -) -install(TARGETS ${PROJECT_NAME}shared - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} -) \ No newline at end of file diff --git a/modules/lithium_search/config.h.in b/modules/lithium_search/config.h.in deleted file mode 100644 index 3397b2be..00000000 --- a/modules/lithium_search/config.h.in +++ /dev/null @@ -1,3 +0,0 @@ -#cmakedefine01 ENABLE_SQLITE -#cmakedefine01 ENABLE_MYSQL -#cmakedefine01 ENABLE_POSTGRESQL \ No newline at end of file diff --git a/modules/lithium_search/config.json b/modules/lithium_search/config.json deleted file mode 100644 index 5fe5b59a..00000000 --- a/modules/lithium_search/config.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Star Search Engine for Lithium", - "description": "Search engine for Lithium", - "version": "1.0.0", - "author": "Max Qian", - "license": "GPLv3 -or- later", - "dependencies": { - "lithium-plugin": "1.0.0", - "atom-core": "1.0.0" - }, - "main": "lithium_search", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/ElementAstro/Lithium/modules/lithium_search" - }, - "keywords": ["lithium", "openngc", "star", "sqlite", "mysql"] -} diff --git a/modules/lithium_search/main.cpp b/modules/lithium_search/main.cpp deleted file mode 100644 index f55980af..00000000 --- a/modules/lithium_search/main.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * lithium_image_main.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-12-1 - -Description: Image processing plugin main - -**************************************************/ - -#include "config.h" - -#include "ongc/ongc.hpp" - -#include - -std::shared_ptr GetInstance() -{ - return std::make_shared("lithium_image", "1.0.0", "Max Qian", "Image processing plugin"); -} - -json GetInfo() -{ - json config; - config["name"] = "lithium_image"; - config["version"] = "1.0.0"; - config["description"] = "Image processing plugin"; - config["author"] = "Max Qian"; - config["email"] = "astro_air@126.com"; - config["url"] = "lightapt.com"; - config["license"] = "GPLv3"; - config["copyright"] = "2023 Max Qian. All rights reserved"; - return config; -} \ No newline at end of file diff --git a/modules/lithium_search/ongc/dso.cpp b/modules/lithium_search/ongc/dso.cpp deleted file mode 100644 index a1238ada..00000000 --- a/modules/lithium_search/ongc/dso.cpp +++ /dev/null @@ -1,244 +0,0 @@ -#include "dso.hpp" - -#include -#include - -DsoObject::DsoObject(const std::string &name) -{ - if (name.empty()) - { - throw std::invalid_argument("Name parameter cannot be empty."); - } - - m_id = 0; - m_name = name; - m_type = "Unknown"; - m_ra = 0.0; - m_dec = 0.0; - m_const = "Unknown"; - m_notngc = "Unknown"; - m_majax = 0.0; - m_minax = 0.0; - m_pa = 0; - m_bmag = 0.0; - m_vmag = 0.0; - m_jmag = 0.0; - m_hmag = 0.0; - m_kmag = 0.0; - m_sbrightn = 0.0; - m_parallax = 0.0; - m_pmra = 0.0; - m_pmdec = 0.0; - m_radvel = 0.0; - m_redshift = 0.0; - m_cstarumag = 0.0; - m_cstarbmag = 0.0; - m_cstarvmag = 0.0; - - std::string catalog, objectname; - std::string tables = "objects JOIN objTypes ON objects.type = objTypes.type "; - tables += "JOIN objIdentifiers ON objects.name = objIdentifiers.name"; - recognize_name(name, catalog, objectname); - std::stringstream params; - - if (catalog == "Messier") - { - params << "messier=\"" << objectname << "\""; - } - else - { - params << "objIdentifiers.identifier=\"" << objectname << "\""; - } - - // Perform query and retrieve object data - std::vector objectData = _queryFetchOne(catalog, tables, params.str()); - - if (objectData.empty()) - { - throw std::runtime_error("Object not found: " + objectname); - } - - // If object is a duplicate and returndup is false, get the main object data - if (objectData[2] == "Dup" && !returndup) - { - if (!objectData[26].empty()) - { - objectname = "NGC" + objectData[26]; - } - else - { - objectname = "IC" + objectData[27]; - } - objectData = _queryFetchOne(catalog, tables, params.str()); - } - - // Assign object properties - _id = std::stoi(objectData[0]); - _name = objectData[1]; - _type = objectData[3]; - _ra = std::stod(objectData[4]); - _dec = std::stod(objectData[5]); - _const = objectData[6]; - _notngc = objectData[33]; - - // Assign optional properties - if (!objectData[7].empty()) - { - _majax = std::stod(objectData[7]); - } - if (!objectData[8].empty()) - { - _minax = std::stod(objectData[8]); - } - if (!objectData[9].empty()) - { - _pa = std::stoi(objectData[9]); - } - if (!objectData[10].empty()) - { - _bmag = std::stod(objectData[10]); - } - if (!objectData[11].empty()) - { - _vmag = std::stod(objectData[11]); - } - if (!objectData[12].empty()) - { - _jmag = std::stod(objectData[12]); - } - if (!objectData[13].empty()) - { - _hmag = std::stod(objectData[13]); - } - if (!objectData[14].empty()) - { - _kmag = std::stod(objectData[14]); - } - if (!objectData[15].empty()) - { - _sbrightn = std::stod(objectData[15]); - } - _hubble = objectData[16]; - if (!objectData[17].empty()) - { - _parallax = std::stod(objectData[17]); - } - if (!objectData[18].empty()) - { - _pmra = std::stod(objectData[18]); - } - if (!objectData[19].empty()) - { - _pmdec = std::stod(objectData[19]); - } - if (!objectData[20].empty()) - { - _radvel = std::stod(objectData[20]); - } - if (!objectData[21].empty()) - { - _redshift = std::stod(objectData[21]); - } - if (!objectData[22].empty()) - { - _cstarumag = std::stod(objectData[22]); - } - if (!objectData[23].empty()) - { - _cstarbmag = std::stod(objectData[23]); - } - if (!objectData[24].empty()) - { - _cstarvmag = std::stod(objectData[24]); - } - _messier = objectData[25]; - _ngc = objectData[26]; - _ic = objectData[27]; - _cstarnames = objectData[28]; - _identifiers = objectData[29]; - _commonnames = objectData[30]; - _nednotes = objectData[31]; - _ongcnotes = objectData[32]; -} - -std::string Dso::to_string() const -{ - return _name + ", " + _type + " in " + _const; -} - -std::string Dso::get_constellation() const -{ - return _const; -} - -std::vector Dso::get_coords() const -{ - std::vector coords; - coords.push_back(std::trunc(rad2deg(_ra) / 15)); - double ms = (rad2deg(_ra) / 15 - coords[0]) * 60; - coords.push_back(std::trunc(ms)); - coords.push_back((ms - coords[1]) * 60); - - double dec = std::trunc(rad2deg(std::abs(_dec))); - ms = (rad2deg(std::abs(_dec)) - dec) * 60; - coords.push_back(dec * (_dec < 0 ? -1 : 1)); - coords.push_back(std::trunc(ms)); - coords.push_back((ms - coords[4]) * 60); - - return coords; -} - -std::string Dso::get_dec() const -{ - std::vector coords = get_coords(); - return std::to_string(coords[3]) + ":" + std::to_string(coords[4]) + ":" + std::to_string(coords[5]); -} - -std::vector Dso::get_dimensions() const -{ - return {_majax, _minax, static_cast(_pa)}; -} - -std::string Dso::get_hubble() const -{ - return _hubble; -} - -int Dso::get_id() const -{ - return _id; -} - -std::tuple, std::vector, std::vector, std::vector> Dso::get_identifiers() const -{ - std::string messier = (!_messier.empty()) ? "M" + _messier : ""; - std::vector ngc, ic, commonNames, other; - - if (!_ngc.empty()) - { - for (const auto &number : split(_ngc, ',')) - { - ngc.push_back("NGC" + number); - } - } - - if (!_ic.empty()) - { - for (const auto &number : split(_ic, ',')) - { - ic.push_back("IC" + number); - } - } - - if (!_commonnames.empty()) - { - commonNames = split(_commonnames, ','); - } - - if (!_identifiers.empty()) - { - other = split(_identifiers, ','); - } - - return std::make_tuple(messier, ngc, ic, commonNames, other); -} \ No newline at end of file diff --git a/modules/lithium_search/ongc/dso.hpp b/modules/lithium_search/ongc/dso.hpp deleted file mode 100644 index cc50b6b5..00000000 --- a/modules/lithium_search/ongc/dso.hpp +++ /dev/null @@ -1,117 +0,0 @@ -#pragma once - -#include -#include -#include -#if _cplusplus >= 201703L -#include -#endif - -/** - * @class DsoObject - * @brief 表示一个天体对象 (Distributed Star Object) - */ -class DsoObject -{ -public: - /** - * @brief 构造函数 - * @param name 天体对象的名称 - */ - DsoObject(const std::string &name); - - /** - * @brief 将天体对象转换为字符串表示形式 - * @return 天体对象的字符串表示形式 - */ - std::string toString() const; - - /** - * @brief 获取天体对象所属的星座 - * @return 星座的名称 - */ - std::string getConstellation() const; - - /** - * @brief 获取天体对象的坐标 - * @tparam T 坐标的类型 , std::vector or std::vector - * @return 天体对象的坐标 - */ - template -#if _cplusplus >= 202002L - requires std::is_arithmetic_v -#else - std::enable_if_t, std::vector> getCoords() const; -#endif - - /** - * @brief 获取天体对象的赤经 - * @return 赤经的字符串表示形式 - */ - std::string getRa() const; - - /** - * @brief 获取天体对象的赤纬 - * @return 赤纬的字符串表示形式 - */ - std::string getDec() const; - - /** - * @brief 获取天体对象的尺寸 - * @return 包含主轴长度、次轴长度和位置角的尺寸数组 - */ - std::vector getDimensions() const; - - /** - * @brief 获取天体对象的哈勃分类 - * @return 哈勃分类的字符串表示形式 - */ - std::string getHubble() const; - - /** - * @brief 获取天体对象的ID - * @return 天体对象的ID - */ - int getId() const; - - /** - * @brief 获取天体对象的所有标识符 - * @return 返回一个包含多个标识符的元组,依次是目录、Messier编号、NGC编号、IC编号和常用名称 - */ - std::tuple, std::vector, std::vector, std::vector> getIdentifiers() const; - -private: - int m_id; /**< 天体对象的ID */ - std::string m_name; /**< 天体对象的名称 */ - std::string m_type; /**< 天体对象的类型 */ - double m_ra; /**< 天体对象的赤经 */ - double m_dec; /**< 天体对象的赤纬 */ - std::string m_const; /**< 天体对象所属的星座 */ - std::string m_notngc; /**< 是否不属于NGC目录 */ - double m_majax; /**< 天体对象的主轴长度 */ - double m_minax; /**< 天体对象的次轴长度 */ - int m_pa; /**< 天体对象的位置角 */ - double m_bmag; /**< 天体对象的B星等 */ - double m_vmag; /**< 天体对象的V星等 */ - double m_jmag; /**< 天体对象的J星等 */ - double m_hmag; /**< 天体对象的H星等 */ - double m_kmag; /**< 天体对象的K星等 */ - double m_sbrightn; /**< 天体对象的表面亮度 */ - std::string m_hubble; /**< 天体对象的哈勃分类 */ - double m_parallax; /**< 天体对象的视差 */ - double m_pmra; /**< 天体对象的赤经年周运动 */ - double m_pmdec; /**< 天体对象的赤纬年周运动 */ - double m_radvel; /**< 天体对象的径向速度 */ - double m_redshift; /**< 天体对象的红移 */ - double m_cstarumag; /**< 中心恒星的U星等 */ - double m_cstarbmag; /**< 中心恒星的B星等 */ - double m_cstarvmag; /**< 中心恒星的V星等 */ - std::string m_messier; /**< 天体对象的Messier编号 */ - std::string m_ngc; /**< 天体对象的NGC编号 */ - std::string m_ic; /**< 天体对象的IC编号 */ - std::string m_cstarnames; /**< 中心恒星的名称 */ - std::string m_identifiers; /**< 天体对象的标识符 */ - std::string m_commonnames; /**< 天体对象的常用名称 */ - std::string m_nednotes; /**< 天体对象的NED注释 */ - std::string m_ongcnotes; /**< 天体对象的OpenNGC注释 */ -}; diff --git a/modules/lithium_search/ongc/ongc.cpp b/modules/lithium_search/ongc/ongc.cpp deleted file mode 100644 index 1921cdb8..00000000 --- a/modules/lithium_search/ongc/ongc.cpp +++ /dev/null @@ -1,450 +0,0 @@ -/* - * ongc.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: C++ Version of PyOngc - -**************************************************/ - -#include "ongc.hpp" -#include "ongc_utils.hpp" - -#include -#include - -#include - -#if ENABLE_FASTHASH -#include "emhash/hash_table8.hpp" -#else -#include -#endif -#include "atom/type/json.hpp" -#include "atom/log/loguru.hpp" - -using json = nlohmann::json; - -#define DBPATH "ognc.db" - -template -std::vector get_identifiers_helper(const std::tuple, std::vector, std::vector, std::vector> &identifiers) -{ - return std::get(identifiers); -} - -template <> -std::vector get_identifiers_helper<0>(const std::tuple, std::vector, std::vector, std::vector> &identifiers) -{ - return {std::get<0>(identifiers)}; -} - -Dso::Dso(const std::string &name, bool returndup = false) -{ - if (name.empty()) - { - throw std::invalid_argument("Name parameter cannot be empty."); - } - - // Initialize object properties - _id = 0; - _ra = 0.0; - _dec = 0.0; - _majax = 0.0; - _minax = 0.0; - _pa = 0; - _bmag = 0.0; - _vmag = 0.0; - _jmag = 0.0; - _hmag = 0.0; - _kmag = 0.0; - _sbrightn = 0.0; - _parallax = 0.0; - _pmra = 0.0; - _pmdec = 0.0; - _radvel = 0.0; - _redshift = 0.0; - _cstarumag = 0.0; - _cstarbmag = 0.0; - _cstarvmag = 0.0; - - std::string catalog, objectname; - std::string tables = "objects JOIN objTypes ON objects.type = objTypes.type "; - tables += "JOIN objIdentifiers ON objects.name = objIdentifiers.name"; - recognize_name(name, catalog, objectname); - std::stringstream params; - - if (catalog == "Messier") - { - params << "messier=\"" << objectname << "\""; - } - else - { - params << "objIdentifiers.identifier=\"" << objectname << "\""; - } - - // Perform query and retrieve object data - std::vector objectData = _queryFetchOne(catalog, tables, params.str()); - - if (objectData.empty()) - { - throw std::runtime_error("Object not found: " + objectname); - } - - // If object is a duplicate and returndup is false, get the main object data - if (objectData[2] == "Dup" && !returndup) - { - if (!objectData[26].empty()) - { - objectname = "NGC" + objectData[26]; - } - else - { - objectname = "IC" + objectData[27]; - } - objectData = _queryFetchOne(catalog, tables, params.str()); - } - - // Assign object properties - _id = std::stoi(objectData[0]); - _name = objectData[1]; - _type = objectData[3]; - _ra = std::stod(objectData[4]); - _dec = std::stod(objectData[5]); - _const = objectData[6]; - _notngc = objectData[33]; - - // Assign optional properties - if (!objectData[7].empty()) - { - _majax = std::stod(objectData[7]); - } - if (!objectData[8].empty()) - { - _minax = std::stod(objectData[8]); - } - if (!objectData[9].empty()) - { - _pa = std::stoi(objectData[9]); - } - if (!objectData[10].empty()) - { - _bmag = std::stod(objectData[10]); - } - if (!objectData[11].empty()) - { - _vmag = std::stod(objectData[11]); - } - if (!objectData[12].empty()) - { - _jmag = std::stod(objectData[12]); - } - if (!objectData[13].empty()) - { - _hmag = std::stod(objectData[13]); - } - if (!objectData[14].empty()) - { - _kmag = std::stod(objectData[14]); - } - if (!objectData[15].empty()) - { - _sbrightn = std::stod(objectData[15]); - } - _hubble = objectData[16]; - if (!objectData[17].empty()) - { - _parallax = std::stod(objectData[17]); - } - if (!objectData[18].empty()) - { - _pmra = std::stod(objectData[18]); - } - if (!objectData[19].empty()) - { - _pmdec = std::stod(objectData[19]); - } - if (!objectData[20].empty()) - { - _radvel = std::stod(objectData[20]); - } - if (!objectData[21].empty()) - { - _redshift = std::stod(objectData[21]); - } - if (!objectData[22].empty()) - { - _cstarumag = std::stod(objectData[22]); - } - if (!objectData[23].empty()) - { - _cstarbmag = std::stod(objectData[23]); - } - if (!objectData[24].empty()) - { - _cstarvmag = std::stod(objectData[24]); - } - _messier = objectData[25]; - _ngc = objectData[26]; - _ic = objectData[27]; - _cstarnames = objectData[28]; - _identifiers = objectData[29]; - _commonnames = objectData[30]; - _nednotes = objectData[31]; - _ongcnotes = objectData[32]; -} - -std::string Dso::to_string() const -{ - return _name + ", " + _type + " in " + _const; -} - -std::string Dso::get_constellation() const -{ - return _const; -} - -std::vector Dso::get_coords() const -{ - std::vector coords; - coords.push_back(std::trunc(rad2deg(_ra) / 15)); - double ms = (rad2deg(_ra) / 15 - coords[0]) * 60; - coords.push_back(std::trunc(ms)); - coords.push_back((ms - coords[1]) * 60); - - double dec = std::trunc(rad2deg(std::abs(_dec))); - ms = (rad2deg(std::abs(_dec)) - dec) * 60; - coords.push_back(dec * (_dec < 0 ? -1 : 1)); - coords.push_back(std::trunc(ms)); - coords.push_back((ms - coords[4]) * 60); - - return coords; -} - -std::string Dso::get_dec() const -{ - std::vector coords = get_coords(); - return std::to_string(coords[3]) + ":" + std::to_string(coords[4]) + ":" + std::to_string(coords[5]); -} - -std::vector Dso::get_dimensions() const -{ - return {_majax, _minax, static_cast(_pa)}; -} - -std::string Dso::get_hubble() const -{ - return _hubble; -} - -int Dso::get_id() const -{ - return _id; -} - -std::tuple, std::vector, std::vector, std::vector> Dso::get_identifiers() const -{ - std::string messier = (!_messier.empty()) ? "M" + _messier : ""; - std::vector ngc, ic, commonNames, other; - - if (!_ngc.empty()) - { - for (const auto &number : split(_ngc, ',')) - { - ngc.push_back("NGC" + number); - } - } - - if (!_ic.empty()) - { - for (const auto &number : split(_ic, ',')) - { - ic.push_back("IC" + number); - } - } - - if (!_commonnames.empty()) - { - commonNames = split(_commonnames, ','); - } - - if (!_identifiers.empty()) - { - other = split(_identifiers, ','); - } - - return std::make_tuple(messier, ngc, ic, commonNames, other); -} - -void DsoObject::recognizeName(const std::string &name, std::string &catalog, std::string &objectname) const -{ - const std::unordered_map patterns = { - {"NGC|IC", R"(^((?:NGC|IC)\s?)(\d{1,4})\s?((NED)(\d{1,2})|[A-Z]{1,2})?$)"}, - {"Messier", R"(^(M\s?)(\d{1,3})$)"}, - {"Barnard", R"(^(B\s?)(\d{1,3})$)"}, - {"Caldwell", R"(^(C\s?)(\d{1,3})$)"}, - {"Collinder", R"(^(CL\s?)(\d{1,3})$)"}, - {"ESO", R"(^(ESO\s?)(\d{1,3})-(\d{1,3})$)"}, - {"Harvard", R"(^(H\s?)(\d{1,2})$)"}, - {"Hickson", R"(^(HCG\s?)(\d{1,3})$)"}, - {"LBN", R"(^(LBN\s?)(\d{1,3})$)"}, - {"Melotte", R"(^(MEL\s?)(\d{1,3})$)"}, - {"MWSC", R"(^(MWSC\s?)(\d{1,4})$)"}, - {"PGC", R"(^((?:PGC|LEDA)\s?)(\d{1,6})$)"}, - {"UGC", R"(^(UGC\s?)(\d{1,5})$)"}}; - - for (const auto &pattern : patterns) - { - std::regex regex(pattern.second); - std::smatch match; - if (std::regex_search(name, match, regex)) - { - catalog = pattern.first; - objectname = match.str(); - break; - } - } -} - -std::vector Dso::_queryFetchOne(const std::string &cols, const std::string &tables, const std::string ¶ms) -{ - sqlite3 *db; - int rc = sqlite3_open_v2(DBPATH, &db, SQLITE_OPEN_READONLY, nullptr); - if (rc != SQLITE_OK) - { - throw std::runtime_error("There was a problem accessing the database file."); - } - - std::vector objectData; - - try - { - sqlite3_stmt *stmt; - std::string query = "SELECT " + cols + " FROM " + tables + " WHERE " + params; - rc = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr); - if (rc != SQLITE_OK) - { - throw std::runtime_error("Failed to prepare the SQL statement."); - } - - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) - { - int numColumns = sqlite3_column_count(stmt); - - for (int i = 0; i < numColumns; ++i) - { - std::string colData = reinterpret_cast(sqlite3_column_text(stmt, i)); - objectData.push_back(colData); - } - } - - sqlite3_finalize(stmt); - } - catch (const std::exception &e) - { - sqlite3_close(db); - throw e; - } - - sqlite3_close(db); - return objectData; -} - -std::vector Dso::split(const std::string &input, char delimiter) const -{ - std::vector tokens; - std::string token; - std::istringstream tokenStream(input); - while (std::getline(tokenStream, token, delimiter)) - { - tokens.push_back(token); - } - return tokens; -} - -std::string join(const std::vector &strings, const std::string &delimiter) const -{ - std::string result; - if (!strings.empty()) - { - result += strings[0]; - for (size_t i = 1; i < strings.size(); ++i) - { - result += delimiter + strings[i]; - } - } - return result; -} - -/* - -int main() -{ - try - { - Dso dso("NGC 1234"); - std::cout << dso.to_string() << std::endl; - std::cout << "Constellation: " << dso.get_constellation() << std::endl; - std::cout << "Coordinates (RA, Dec): " << dso.get_coords()[0] << ", " << dso.get_coords()[1] << ", " << dso.get_coords()[2] << ", " << dso.get_coords()[3] << ", " << dso.get_coords()[4] << ", " << dso.get_coords()[5] << std::endl; - std::cout << "Declination: " << dso.get_dec() << std::endl; - std::cout << "Dimensions (Majax, Minax, PA): " << dso.get_dimensions()[0] << ", " << dso.get_dimensions()[1] << ", " << dso.get_dimensions()[2] << std::endl; - std::cout << "Hubble Type: " << dso.get_hubble() << std::endl; - std::cout << "Object ID: " << dso.get_id() << std::endl; - auto identifiers = dso.get_identifiers(); - std::cout << "Messier: " << std::get<0>(identifiers) << std::endl; - std::cout << "NGC: "; - for (const auto &ngc : std::get<1>(identifiers)) - { - std::cout << ngc << " "; - } - std::cout << std::endl; - std::cout << "IC: "; - for (const auto &ic : std::get<2>(identifiers)) - { - std::cout << ic << " "; - } - std::cout << std::endl; - std::cout << "Common Names: "; - for (const auto &commonName : std::get<3>(identifiers)) - { - std::cout << commonName << " "; - } - std::cout << std::endl; - std::cout << "Other Identifiers: "; - for (const auto &identifier : std::get<4>(identifiers)) - { - std::cout << identifier << " "; - } - std::cout << std::endl; - } - catch (const std::exception &e) - { - std::cout << "Error: " << e.what() << std::endl; - } - return 0; -} - -*/ \ No newline at end of file diff --git a/modules/lithium_search/ongc/ongc.hpp b/modules/lithium_search/ongc/ongc.hpp deleted file mode 100644 index 4e2ad458..00000000 --- a/modules/lithium_search/ongc/ongc.hpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * ongc.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: C++ Version of PyOngc - -**************************************************/ - -#pragma once - -#include "dso.hpp" -#include "core/plugin/plugin.hpp" - -class OpenNGC : public Plugin -{ -public: - OpenNGC(const std::string &path, const std::string &version, const std::string &author, const std::string &description); - ~OpenNGC(); - -private: - - void recognizeName(const std::string &name, std::string &catalog, std::string &objectname); - - -} \ No newline at end of file diff --git a/modules/lithium_search/ongc/ongc_utils.cpp b/modules/lithium_search/ongc/ongc_utils.cpp deleted file mode 100644 index b122113d..00000000 --- a/modules/lithium_search/ongc/ongc_utils.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "ongc_utils.hpp" - -#include - -#ifndef M_PI -#define M_PI (3.14159265358979323846) -#endif - -double rad2deg(const double &radians) -{ - const double conversionFactor = 180.0 / M_PI; - return radians * conversionFactor; -} - -double deg2rad(const double °rees) -{ - const double conversionFactor = M_PI / 180.0; - return degrees * conversionFactor; -} - -template -T clamp(T value, T min, T max) -{ - if (value < min) - { - return min; - } - else if (value > max) - { - return max; - } - else - { - return value; - } -} - -template -T lerp(T start, T end, double t) -{ - return start + t * (end - start); -} - -double smoothstep(double edge0, double edge1, double x) -{ - x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); - return x * x * (3.0 - 2.0 * x); -} - -double normalizeAngle(double angle) -{ - while (angle <= -180.0) - { - angle += 360.0; - } - - while (angle > 180.0) - { - angle -= 360.0; - } - - return angle; -} - -double interpolateAngle(double start, double end, double t) -{ - double difference = normalizeAngle(end - start); - return start + difference * t; -} diff --git a/modules/lithium_search/ongc/ongc_utils.hpp b/modules/lithium_search/ongc/ongc_utils.hpp deleted file mode 100644 index 46879d7a..00000000 --- a/modules/lithium_search/ongc/ongc_utils.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -double rad2deg(const double &radians); -double deg2rad(const double °rees); -double smoothstep(double edge0, double edge1, double x); -double normalizeAngle(double angle); -double interpolateAngle(double start, double end, double t); \ No newline at end of file diff --git a/modules/oxygen_celestial/calculator.py b/modules/oxygen_celestial/calculator.py deleted file mode 100644 index 2deaaef1..00000000 --- a/modules/oxygen_celestial/calculator.py +++ /dev/null @@ -1,51 +0,0 @@ -# coding=utf-8 - -""" - -Copyright(c) 2022-2023 Max Qian - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License version 3 as published by the Free Software Foundation. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. -You should have received a copy of the GNU Library General Public License -along with this library; see the file COPYING.LIB. If not, write to -the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -Boston, MA 02110-1301, USA. - -""" - -import datetime - -from astropy import units as u -from astropy.coordinates import AltAz, EarthLocation, SkyCoord -from astropy.time import Time -from astropy.coordinates import FK5 - -location = None - -def convert_j2000_to_jnow(star_object : SkyCoord) -> SkyCoord: - """ - Convert coordinates in J2000 format to JNow format - """ - tmp = star_object.fk5 - return tmp.transform_to(FK5(equinox=f'J{datetime.datetime.today().year}')) - -def convert_radec_to_azalt(star_object : SkyCoord) -> SkyCoord: - """ - Convert coordinates in RA/DEC format to AZ/ALT format - """ - return star_object.transform_to(AltAz( - obstime=Time(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))-8*u.hour, - location=location) - ) - -def set_location(lat : str | float , lon : str | float , ele : float) -> EarthLocation: - """ - Set the location - """ - global location - location = EarthLocation(lat=float(lat)*u.deg, lon=float(lon)*u.deg, height=ele*u.m) \ No newline at end of file diff --git a/modules/oxygen_celestial/createdb.py b/modules/oxygen_celestial/createdb.py deleted file mode 100644 index 171852fe..00000000 --- a/modules/oxygen_celestial/createdb.py +++ /dev/null @@ -1,140 +0,0 @@ -# coding=utf-8 - -""" - -Copyright(c) 2022-2023 Max Qian - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License version 3 as published by the Free Software Foundation. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. -You should have received a copy of the GNU Library General Public License -along with this library; see the file COPYING.LIB. If not, write to -the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -Boston, MA 02110-1301, USA. - -""" - -import os -import csv -import numpy as np -import sqlite3 - -outputFile = os.path.join(os.path.dirname(__file__), "data.db") - -# Dictionaries -objectTypes = { - "*": "Star", - "**": "Double star", - "*Ass": "Association of stars", - "OCl": "Open Cluster", - "GCl": "Globular Cluster", - "Cl+N": "Star cluster + Nebula", - "G": "Galaxy", - "GPair": "Galaxy Pair", - "GTrpl": "Galaxy Triplet", - "GGroup": "Group of galaxies", - "PN": "Planetary Nebula", - "HII": "HII Ionized region", - "DrkN": "Dark Nebula", - "EmN": "Emission Nebula", - "Neb": "Nebula", - "RfN": "Reflection Nebula", - "SNR": "Supernova remnant", - "Nova": "Nova star", - "NonEx": "Nonexistent object", - "Other": "Object of other/unknown type", - "Dup": "Duplicated record", -} - -# Create db -try: - db = sqlite3.connect(outputFile) - cursor = db.cursor() - - # Create objects types table - cursor.execute("DROP TABLE IF EXISTS objTypes") - cursor.execute( - "CREATE TABLE IF NOT EXISTS objTypes(" - "type TEXT PRIMARY KEY NOT NULL, " - "typedesc TEXT NOT NULL)" - ) - listTypes = objectTypes.items() - cursor.executemany("INSERT INTO objTypes VALUES(?,?)", listTypes) - - # Create main objects table - cursor.execute("DROP TABLE IF EXISTS objects") - cursor.execute( - "CREATE TABLE IF NOT EXISTS objects(" - "Name TEXT NOT NULL UNIQUE, " - "Type TEXT NOT NULL, " - "RA REAL, " - "Dec REAL, " - "Const TEXT) " - ) - - # Create object identifiers table - cursor.execute("DROP TABLE IF EXISTS objIdentifiers") - cursor.execute( - "CREATE TABLE IF NOT EXISTS objIdentifiers(" - "id INTEGER PRIMARY KEY NOT NULL, " - "name TEXT NOT NULL, " - "identifier TEXT NOT NULL UNIQUE)" - ) - filename = "data.csv" - if True: - notngc = True if filename != "data.csv" else False - with open(filename, "r") as csvFile: - reader = csv.DictReader(csvFile, delimiter=",") - # List of columns that are not text and should be transformed in NULL if empty - columns_maybe_null = [ - - ] - for line in reader: - for column in columns_maybe_null: - if line[column] == "": - line[column] = None - # Convert RA and Dec in radians - if line["RA"] != "": - ra_array = np.array([float(x) for x in line["RA"].split(":")]) - ra_rad = np.radians(np.sum(ra_array * [15, 1 / 4, 1 / 240])) - else: - ra_rad = None - if line["Dec"] != "": - dec_array = np.array([float(x) for x in line["Dec"].split(":")]) - if np.signbit(dec_array[0]): - dec_rad = np.radians( - np.sum(dec_array * [1, -1 / 60, -1 / 3600]) - ) - else: - dec_rad = np.radians(np.sum(dec_array * [1, 1 / 60, 1 / 3600])) - else: - dec_rad = None - cursor.execute( - "INSERT INTO objects(name,type,ra,dec,const)" - "VALUES(?,?,?,?,?)", - ( - line["Name"], - line["Type"], - ra_rad, - dec_rad, - line["Const"], - ), - ) - cursor.execute( - "INSERT INTO objIdentifiers(name,identifier) VALUES(?,?)", - (line["Name"], line["Name"].upper()), - ) - - cursor.execute( - 'CREATE UNIQUE INDEX "idx_identifiers" ON "objIdentifiers" ("identifier");' - ) - db.commit() -except Exception as e: - db.rollback() - raise e -finally: - db.close() diff --git a/modules/oxygen_celestial/data.csv b/modules/oxygen_celestial/data.csv deleted file mode 100644 index 501667f1..00000000 --- a/modules/oxygen_celestial/data.csv +++ /dev/null @@ -1,13969 +0,0 @@ -Name,Type,RA,Dec,Const -IC0001,**,00:08:27.05,+27:43:03.6,Peg -IC0002,G,00:11:00.88,-12:49:22.3,Cet -IC0003,G,00:12:06.09,-00:24:54.8,Psc -IC0004,G,00:13:26.94,+17:29:11.2,Peg -IC0005,G,00:17:34.93,-09:32:36.1,Cet -IC0006,G,00:18:55.04,-03:16:33.9,Psc -IC0007,G,00:18:53.16,+10:35:40.9,Psc -IC0008,G,00:19:02.72,-03:13:19.5,Psc -IC0009,G,00:19:43.98,-14:07:18.8,Cet -IC0010,G,00:20:17.34,+59:18:13.6,Cas -IC0011,Dup,00:52:59.35,+56:37:18.8,Cas -IC0012,G,00:20:15.05,-02:39:11.2,Psc -IC0013,G,00:20:20.09,+07:42:01.8,Psc -IC0014,**,00:22:31.29,+10:29:24.9,Psc -IC0015,G,00:27:57.59,-00:03:40.6,Cet -IC0016,G,00:28:07.65,-13:05:38.1,Cet -IC0017,G,00:28:29.78,+02:38:55.0,Cet -IC0018,G,00:28:34.96,-11:35:12.1,Cet -IC0019,G,00:28:39.48,-11:38:26.8,Cet -IC0020,G,00:28:39.68,-13:00:37.1,Cet -IC0021,G,00:29:10.43,-00:09:49.6,Cet -IC0022,G,00:29:33.17,-09:04:50.7,Cet -IC0023,G,00:30:50.80,-12:43:12.9,Cet -IC0024,**,00:31:16.63,+30:50:21.5,And -IC0025,G,00:31:12.09,-00:24:26.5,Cet -IC0026,Dup,00:31:45.94,-13:20:14.9,Cet -IC0027,G,00:33:06.24,-13:22:17.4,Cet -IC0028,G,00:33:08.72,-13:27:22.6,Cet -IC0029,G,00:34:10.80,-02:10:39.5,Cet -IC0030,G,00:34:14.72,-02:05:04.5,Cet -IC0031,G,00:34:24.63,+12:16:05.4,Psc -IC0032,G,00:35:01.69,-02:08:30.1,Cet -IC0033,G,00:35:05.16,-02:08:15.7,Cet -IC0034,G,00:35:36.42,+09:07:27.3,Psc -IC0035,G,00:37:39.88,+10:21:28.7,Psc -IC0036,G,00:37:49.65,-15:26:28.6,Cet -IC0037,G,00:38:34.18,-15:21:31.3,Cet -IC0038,G,00:38:38.76,-15:25:11.4,Cet -IC0039,Dup,00:39:08.40,-14:10:22.2,Cet -IC0040,G,00:39:21.40,+02:27:22.4,Cet -IC0041,G,00:39:40.37,-14:10:27.6,Cet -IC0042,G,00:41:05.84,-15:25:41.1,Cet -IC0043,G,00:42:22.06,+29:38:29.9,And -IC0044,Dup,00:42:15.88,+00:50:43.8,Cet -IC0045,Other,00:42:36.34,+29:39:18.9,And -IC0046,G,00:42:57.97,+27:15:12.5,And -IC0047,G,00:42:55.01,-13:44:26.6,Cet -IC0048,G,00:43:34.47,-08:11:11.4,Cet -IC0049,G,00:43:56.13,+01:51:01.0,Cet -IC0050,G,00:46:05.71,-09:30:10.8,Cet -IC0051,G,00:46:24.22,-13:26:32.5,Cet -IC0052,G,00:48:23.78,+04:05:30.7,Psc -IC0053,G,00:50:40.77,+10:36:01.0,Psc -IC0054,**,00:50:46.85,-02:17:16.0,Cet -IC0055,G,00:51:42.39,+07:43:06.7,Psc -IC0056,G,00:51:29.94,-12:50:39.5,Cet -IC0057,G,00:54:48.50,+11:50:28.3,Psc -IC0058,G,00:55:02.44,-13:40:41.1,Cet -IC0059,RfN,00:57:28.61,+61:08:37.2,Cas -IC0060,G,00:56:04.23,-13:21:28.5,Cet -IC0061,G,00:57:07.20,+07:30:25.5,Psc -IC0062,G,00:58:43.94,+11:48:28.8,Psc -IC0063,HII,00:59:28.84,+60:54:42.1,Cas -IC0064,G,00:59:24.42,+27:03:32.6,Psc -IC0065,G,01:00:55.42,+47:40:55.1,And -IC0066,G,01:00:32.50,+30:47:50.0,Psc -IC0067,NonEx,01:00:17.66,-06:54:38.7,Cet -IC0068,NonEx,01:00:21.65,-06:56:38.8,Cet -IC0069,G,01:01:23.66,+31:02:27.0,Psc -IC0070,G,01:01:03.95,+00:03:02.7,Cet -IC0071,*,01:01:19.26,-06:46:01.8,Cet -IC0072,*,01:01:34.71,-06:46:40.6,Cet -IC0073,G,01:04:52.95,+04:46:01.7,Psc -IC0074,G,01:05:55.95,+04:05:24.9,Psc -IC0075,G,01:07:11.61,+10:50:12.8,Psc -IC0076,G,01:08:11.71,-04:33:16.2,Cet -IC0077,G,01:08:43.75,-15:25:15.3,Cet -IC0078,G,01:08:47.70,-15:50:42.3,Cet -IC0079,G,01:08:49.71,-15:56:54.9,Cet -IC0080,GPair,01:08:50.90,-15:24:27.0,Cet -IC0080 NED01,G,01:08:50.70,-15:24:32.2,Cet -IC0080 NED02,G,01:08:51.13,-15:24:22.7,Cet -IC0081,G,01:09:22.26,-01:41:45.1,Cet -IC0082,G,01:09:05.79,-16:00:01.1,Cet -IC0083,G,01:10:29.78,+01:41:21.7,Cet -IC0084,G,01:11:25.60,+01:38:24.7,Cet -IC0085,*,01:11:49.55,-00:27:08.9,Cet -IC0086,G,01:13:28.52,-16:14:30.0,Cet -IC0087,G,01:14:15.79,+00:45:55.2,Cet -IC0088,G,01:14:31.30,+00:47:30.3,Cet -IC0089,Dup,01:16:03.61,+04:17:38.8,Psc -IC0090,G,01:16:30.35,-07:58:37.7,Cet -IC0091,G,01:18:39.43,+02:33:13.2,Cet -IC0092,Dup,01:19:48.50,+32:46:04.0,Psc -IC0093,G,01:19:02.34,-17:03:37.4,Cet -IC0094,*,01:20:05.49,+32:43:02.4,Psc -IC0095,G,01:19:17.88,-12:34:26.9,Cet -IC0096,G,01:20:33.22,+29:37:02.1,Psc -IC0097,Dup,01:20:02.00,+14:51:39.8,Psc -IC0098,G,01:20:54.88,-12:36:17.0,Cet -IC0099,G,01:22:27.36,-12:57:09.2,Cet -IC0100,G,01:22:53.96,-04:38:34.9,Cet -IC0101,G,01:24:08.55,+09:55:49.9,Psc -IC0102,G,01:24:26.33,+09:53:11.6,Psc -IC0103,G,01:24:36.44,+02:02:39.3,Cet -IC0104,**,01:24:33.60,-01:27:24.2,Cet -IC0105,G,01:24:46.25,+02:04:31.0,Cet -IC0106,Dup,01:24:41.65,-01:35:13.5,Cet -IC0107,G,01:25:24.66,+14:51:52.6,Psc -IC0108,G,01:24:38.96,-12:38:08.1,Cet -IC0109,G,01:25:13.09,+02:03:58.4,Cet -IC0110,Other,01:25:46.60,+33:30:52.9,Psc -IC0111,Other,01:26:00.13,+33:29:49.6,Psc -IC0112,G,01:26:03.02,+11:26:34.7,Psc -IC0113,G,01:26:25.50,+19:11:31.1,Psc -IC0114,G,01:26:22.58,+09:54:35.8,Psc -IC0115,G,01:26:54.44,+19:12:52.9,Psc -IC0116,G,01:26:50.57,-04:58:56.2,Cet -IC0117,Dup,01:27:25.42,-01:54:46.7,Cet -IC0118,G,01:27:36.04,-04:59:50.9,Cet -IC0119,G,01:27:55.02,-02:02:25.7,Cet -IC0120,G,01:28:12.96,-01:54:55.9,Cet -IC0121,G,01:28:21.78,+02:30:46.9,Cet -IC0122,G,01:28:13.20,-14:50:20.0,Cet -IC0123,G,01:28:51.46,+02:26:47.2,Cet -IC0124,*,01:29:09.08,-01:56:13.3,Cet -IC0125,G,01:29:18.35,-13:16:47.4,Cet -IC0126,G,01:29:47.86,-01:59:01.5,Cet -IC0127,G,01:29:47.61,-06:58:48.2,Cet -IC0128,G,01:31:23.87,-12:37:27.8,Cet -IC0129,G,01:31:31.26,-12:39:15.8,Cet -IC0130,G,01:31:28.74,-15:35:29.8,Cet -IC0131,HII,01:33:14.58,+30:45:11.7,Tri -IC0132,HII,01:33:15.92,+30:56:44.2,Tri -IC0133,HII,01:33:15.18,+30:53:18.2,Tri -IC0134,*,01:33:25.00,+30:54:02.4,Tri -IC0135,HII,01:34:15.53,+30:37:12.3,Tri -IC0136,*Ass,01:34:16.04,+30:33:43.4,Tri -IC0137,*Ass,01:33:38.91,+30:31:21.0,Tri -IC0138,G,01:33:01.98,-00:41:23.4,Cet -IC0139,*Ass,01:33:59.24,+30:34:02.8,Tri -IC0140,*Ass,01:33:58.17,+30:33:00.5,Tri -IC0141,G,01:32:51.71,-14:48:52.6,Cet -IC0142,HII,01:33:55.53,+30:45:28.1,Tri -IC0143,HII,01:34:10.93,+30:46:35.1,Tri -IC0144,G,01:37:40.85,-13:18:52.8,Cet -IC0145,G,01:38:38.48,+00:44:29.4,Cet -IC0146,Dup,01:38:39.79,-17:49:52.3,Cet -IC0147,G,01:39:59.75,-14:51:45.5,Cet -IC0148,G,01:42:26.97,+13:58:37.3,Psc -IC0149,G,01:42:25.38,-16:18:01.5,Cet -IC0150,G,01:42:57.52,+04:12:00.6,Psc -IC0151,Other,01:43:57.47,+13:12:09.4,Psc -IC0152,Other,01:44:07.39,+13:02:09.1,Psc -IC0153,Other,01:44:36.21,+12:37:44.2,Psc -IC0154,G,01:45:16.27,+10:38:57.1,Psc -IC0155,Other,01:47:32.33,+60:36:39.0,Cas -IC0156,G,01:45:29.21,+10:33:09.8,Psc -IC0157,Other,01:45:42.40,+12:52:24.0,Psc -IC0158,G,01:45:53.52,-06:56:08.5,Cet -IC0159,G,01:46:25.05,-08:38:11.8,Cet -IC0160,G,01:46:29.56,-13:14:51.8,Cet -IC0161,G,01:48:43.74,+10:30:28.4,Psc -IC0162,G,01:48:53.43,+10:31:17.8,Psc -IC0163,G,01:49:14.99,+20:42:40.7,Ari -IC0164,G,01:49:08.41,-03:54:15.8,Cet -IC0165,Dup,01:50:14.02,+27:38:44.4,Tri -IC0166,OCl,01:52:23.82,+61:51:09.4,Cas -IC0167,G,01:51:08.56,+21:54:46.1,Ari -IC0168,G,01:50:27.63,-08:31:22.6,Cet -IC0169,G,01:50:39.36,-12:40:46.6,Cet -IC0170,G,01:51:57.52,-08:31:03.4,Cet -IC0171,G,01:55:10.21,+35:16:54.8,Tri -IC0172,G,01:54:54.23,+00:48:40.4,Cet -IC0173,G,01:55:57.15,+01:17:06.6,Cet -IC0174,G,01:56:16.08,+03:45:42.7,Psc -IC0175,G,01:56:18.86,+01:19:56.7,Cet -IC0176,G,01:56:53.40,-02:01:08.3,Cet -IC0177,G,01:57:00.58,-00:05:23.5,Cet -IC0178,G,01:58:54.87,+36:40:28.9,And -IC0179,G,02:00:11.50,+38:01:16.8,And -IC0180,G,02:00:00.41,+23:36:15.6,Ari -IC0181,G,02:00:02.33,+23:39:31.1,Ari -IC0182,G,01:59:51.81,+07:24:42.6,Psc -IC0183,G,01:59:34.05,-05:20:49.7,Cet -IC0184,G,01:59:51.23,-06:50:25.4,Cet -IC0185,G,02:00:06.04,-01:31:42.0,Cet -IC0186,GTrpl,02:00:24.50,-01:33:06.2,Cet -IC0186A,G,02:00:24.00,-01:33:10.3,Cet -IC0186B,G,02:00:25.45,-01:32:59.3,Cet -IC0186 NED03,G,02:00:25.40,-01:33:03.0,Cet -IC0187,G,02:01:30.76,+26:28:51.5,Tri -IC0188,G,02:01:46.50,+26:32:48.8,Tri -IC0189,G,02:01:52.91,+23:33:05.4,Ari -IC0190,G,02:02:07.28,+23:32:59.4,Ari -IC0191,Dup,02:02:29.33,+18:22:22.8,Ari -IC0192,G,02:02:32.39,+16:00:51.0,Ari -IC0193,G,02:02:30.98,+11:05:35.1,Ari -IC0194,G,02:03:05.22,+02:36:51.3,Psc -IC0195,G,02:03:44.61,+14:42:33.4,Ari -IC0196,G,02:03:49.80,+14:44:20.9,Ari -IC0197,G,02:04:04.93,+02:47:12.6,Psc -IC0198,G,02:06:03.12,+09:17:44.0,Psc -IC0199,G,02:06:19.40,+09:13:38.7,Psc -IC0200,G,02:05:26.64,+31:10:31.2,Tri -IC0201,G,02:07:15.29,+09:06:54.2,Cet -IC0202,G,02:07:28.66,+09:10:05.9,Cet -IC0203,G,02:07:29.65,+09:07:21.5,Cet -IC0204,G,02:07:27.08,-01:25:48.7,Cet -IC0205,G,02:07:27.43,-02:05:28.6,Cet -IC0206,G,02:09:30.68,-06:58:06.2,Cet -IC0207,G,02:09:39.35,-06:55:19.9,Cet -IC0208,G,02:08:27.74,+06:23:41.7,Cet -IC0209,G,02:08:58.71,-07:03:32.1,Cet -IC0210,G,02:09:28.28,-09:40:49.2,Cet -IC0211,G,02:11:08.00,+03:51:09.0,Cet -IC0212,G,02:13:38.26,+16:35:38.2,Ari -IC0213,G,02:14:04.28,+16:27:21.2,Ari -IC0214,G,02:14:05.59,+05:10:23.7,Cet -IC0215,G,02:14:09.46,-06:48:22.8,Cet -IC0216,G,02:15:55.52,-02:00:54.4,Cet -IC0217,G,02:16:10.44,-11:55:36.2,Cet -IC0218,G,02:17:07.25,+01:16:56.4,Cet -IC0219,G,02:18:38.83,-06:54:12.0,Cet -IC0220,G,02:19:11.73,-12:46:53.9,Cet -IC0221,G,02:22:40.90,+28:15:25.1,Tri -IC0222,G,02:22:47.85,+11:38:18.0,Ari -IC0223,G,02:22:01.10,-20:44:45.3,Cet -IC0224,G,02:24:45.14,-12:33:52.1,Cet -IC0225,G,02:26:28.29,+01:09:37.9,Cet -IC0226,G,02:27:45.93,+28:12:31.8,Tri -IC0227,G,02:28:03.61,+28:10:31.2,Tri -IC0228,Dup,02:26:41.53,-14:30:56.3,Cet -IC0229,Other,02:27:22.09,-23:48:46.8,Cet -IC0230,G,02:28:47.29,-10:49:52.9,Cet -IC0231,G,02:29:56.39,+01:10:44.7,Cet -IC0232,G,02:31:11.62,+01:15:56.3,Cet -IC0233,G,02:31:40.70,+02:48:35.7,Cet -IC0234,G,02:31:37.72,-00:08:24.9,Cet -IC0235,G,02:32:50.79,+20:38:28.5,Ari -IC0236,G,02:32:55.81,-00:07:52.5,Cet -IC0237,G,02:33:31.57,+01:08:23.6,Cet -IC0238,G,02:35:22.68,+12:50:15.8,Ari -IC0239,G,02:36:27.88,+38:58:11.7,And -IC0240,Other,02:38:58.57,+41:43:09.9,Per -IC0241,G,02:37:54.50,+02:19:40.9,Cet -IC0242,**,02:38:23.93,-06:56:02.2,Cet -IC0243,G,02:38:32.16,-06:54:07.7,Cet -IC0244,G,02:39:24.73,+02:43:43.7,Cet -IC0245,G,02:38:54.64,-14:18:20.0,Cet -IC0246,G,02:40:28.60,+02:28:43.1,Cet -IC0247,G,02:40:08.78,-11:44:01.6,Cet -IC0248,G,02:41:25.83,+17:48:44.0,Ari -IC0249,Dup,02:41:02.49,-06:56:09.3,Cet -IC0250,G,02:40:54.27,-13:18:48.8,Cet -IC0251,G,02:41:13.85,-14:57:28.2,Cet -IC0252,G,02:41:45.13,-14:50:53.7,Cet -IC0253,G,02:42:05.75,-15:02:48.6,Cet -IC0254,G,02:42:04.98,-15:06:23.9,Cet -IC0255,G,02:47:03.21,+16:17:16.9,Ari -IC0256,G,02:49:40.30,+46:57:16.8,Per -IC0257,G,02:49:45.44,+46:58:34.3,Per -IC0258,G,02:49:46.10,+41:03:06.0,Per -IC0259,G,02:49:40.88,+41:03:18.4,Per -IC0260,G,02:51:00.88,+46:57:17.2,Per -IC0261,Dup,02:49:04.09,-14:28:14.5,Eri -IC0262,G,02:51:43.27,+42:49:41.8,Per -IC0263,G,02:49:39.96,-00:04:11.5,Cet -IC0264,G,02:48:47.62,-00:06:33.1,Cet -IC0265,G,02:54:43.98,+41:39:19.2,Per -IC0266,G,02:55:04.83,+42:15:44.4,Per -IC0267,G,02:53:50.25,+12:50:57.2,Ari -IC0268,G,02:55:26.95,-14:06:10.8,Eri -IC0269,G,02:55:26.49,-14:04:00.5,Eri -IC0270,G,02:55:44.19,-14:12:28.4,Eri -IC0271,G,02:55:59.45,-12:00:28.3,Eri -IC0272,G,02:56:06.44,-14:11:11.9,Eri -IC0273,G,02:57:10.78,+02:46:30.2,Cet -IC0274,Other,03:00:05.23,+44:12:47.4,Per -IC0275,GTrpl,03:00:57.30,+44:20:54.0,Per -IC0275 NED01,G,03:00:55.87,+44:20:46.7,Per -IC0275 NED02,G,03:00:55.90,+44:21:01.3,Per -IC0275 NED03,G,03:00:58.67,+44:21:00.6,Per -IC0276,G,02:58:41.06,-15:42:11.2,Eri -IC0277,G,02:59:50.60,+02:46:16.9,Cet -IC0278,G,03:01:30.44,+37:45:57.6,Per -IC0279,G,03:01:12.21,+16:12:33.2,Ari -IC0280,Other,03:03:03.61,+42:21:33.5,Per -IC0281,Dup,03:04:37.13,+42:21:46.2,Per -IC0282,Dup,03:06:13.24,+41:50:56.2,Per -IC0283,G,03:03:50.46,-00:12:16.0,Cet -IC0284,G,03:06:09.91,+42:22:18.9,Per -IC0285,G,03:04:06.22,-12:00:55.6,Eri -IC0286,Other,03:04:47.22,-06:29:04.3,Eri -IC0287,G,03:04:57.82,-12:04:13.6,Eri -IC0288,G,03:07:32.91,+42:23:15.2,Per -IC0289,PN,03:10:19.26,+61:19:00.4,Cas -IC0290,G,03:09:42.74,+40:58:27.2,Per -IC0291,G,03:07:26.45,-12:35:14.8,Eri -IC0292,G,03:10:12.92,+40:45:56.4,Per -IC0293,G,03:10:56.16,+41:08:13.6,Per -IC0294,G,03:11:03.11,+40:37:19.6,Per -IC0295,Other,03:11:00.74,+40:36:54.9,Per -IC0296,Other,03:11:04.77,+40:37:36.7,Per -IC0297,**,03:13:18.38,+42:08:55.6,Per -IC0298,GPair,03:11:18.90,+01:18:53.0,Cet -IC0298A,G,03:11:18.50,+01:18:54.1,Cet -IC0298B,G,03:11:19.53,+01:18:48.2,Cet -IC0299,G,03:11:02.55,-13:06:34.5,Eri -IC0300,G,03:14:16.02,+42:24:55.2,Per -IC0301,G,03:14:47.71,+42:13:21.5,Per -IC0302,G,03:12:51.27,+04:42:25.3,Cet -IC0303,G,03:12:40.87,-11:41:23.8,Eri -IC0304,G,03:15:01.37,+37:52:54.6,Per -IC0305,G,03:15:03.80,+37:51:36.0,Per -IC0306,G,03:13:00.21,-11:42:56.5,Eri -IC0307,G,03:13:45.21,-00:14:29.2,Cet -IC0308,G,03:16:15.83,+41:10:50.7,Per -IC0309,G,03:16:06.29,+40:48:16.4,Per -IC0310,G,03:16:42.98,+41:19:29.9,Per -IC0311,G,03:16:46.73,+40:00:13.0,Per -IC0312,G,03:18:08.61,+41:45:14.8,Per -IC0313,G,03:20:57.90,+41:53:37.8,Per -IC0314,Dup,03:18:49.81,-01:58:24.0,Eri -IC0315,G,03:19:09.34,+04:02:18.8,Cet -IC0316,GPair,03:21:19.90,+41:55:50.0,Per -IC0316 NED01,G,03:21:19.95,+41:55:44.2,Per -IC0316 NED02,G,03:21:19.90,+41:55:54.9,Per -IC0317,G,03:18:55.51,-12:44:24.7,Eri -IC0318,G,03:20:43.83,-14:34:05.6,Eri -IC0319,*,03:23:29.05,+41:24:58.9,Per -IC0320,G,03:25:59.18,+40:47:20.5,Per -IC0321,G,03:24:29.59,-14:59:07.7,Eri -IC0322,G,03:26:00.47,+03:40:50.0,Tau -IC0323,Other,03:29:33.55,+41:51:18.3,Per -IC0324,Dup,03:26:28.29,-21:21:19.1,Eri -IC0325,G,03:30:48.86,-07:02:48.2,Eri -IC0326,G,03:30:36.61,-14:25:32.0,Eri -IC0327,G,03:31:09.98,-14:41:31.7,Eri -IC0328,G,03:31:10.95,-14:38:16.3,Eri -IC0329,G,03:32:01.37,+00:16:46.0,Tau -IC0330,G,03:32:08.03,+00:21:12.2,Tau -IC0331,G,03:32:19.08,+00:16:57.2,Tau -IC0332,G,03:32:37.42,+01:22:57.2,Tau -IC0333,Other,03:34:03.38,-04:35:05.6,Eri -IC0334,G,03:45:17.08,+76:38:17.9,Cam -IC0335,G,03:35:31.04,-34:26:49.4,For -IC0336,Neb,03:37:57.04,+23:21:47.3,Tau -IC0337,Other,03:37:04.30,-06:43:16.3,Eri -IC0338,G,03:37:38.10,+03:07:07.6,Tau -IC0339,*,03:38:02.23,-18:23:52.0,Eri -IC0340,G,03:39:29.11,-13:06:54.4,Eri -IC0341,Neb,03:40:55.69,+21:57:36.7,Tau -IC0342,G,03:46:48.50,+68:05:46.9,Cam -IC0343,G,03:40:07.14,-18:26:36.5,Eri -IC0344,G,03:41:29.53,-04:39:57.1,Eri -IC0345,G,03:41:09.13,-18:18:50.9,Eri -IC0346,G,03:41:44.66,-18:16:01.1,Eri -IC0347,G,03:42:32.65,-04:17:55.1,Eri -IC0348,Cl+N,03:44:34.19,+32:09:46.2,Per -IC0349,RfN,03:46:20.11,+23:56:23.3,Tau -IC0350,G,03:44:36.65,-11:48:02.7,Eri -IC0351,PN,03:47:33.01,+35:02:48.9,Per -IC0352,G,03:47:37.43,-08:43:54.6,Eri -IC0353,Neb,03:53:01.07,+25:50:52.8,Tau -IC0354,Neb,03:53:57.91,+23:08:49.2,Tau -IC0355,G,03:53:46.26,+19:58:26.1,Tau -IC0356,G,04:07:46.91,+69:48:44.8,Cam -IC0357,G,04:03:44.00,+22:09:32.8,Tau -IC0358,G,04:03:42.88,+19:53:41.8,Tau -IC0359,G,04:12:28.36,+27:42:07.1,Tau -IC0359A,RfN,04:18:41.00,+28:17:24.0,Tau -IC0360,Neb,04:09:02.57,+26:07:52.3,Tau -IC0361,OCl,04:18:50.70,+58:14:58.2,Cam -IC0362,G,04:16:42.43,-12:12:00.4,Eri -IC0363,G,04:18:55.42,+03:01:58.9,Tau -IC0364,G,04:19:06.70,+03:11:20.2,Tau -IC0365,G,04:19:14.14,+03:20:54.0,Tau -IC0366,G,04:19:41.54,+02:21:35.3,Tau -IC0367,G,04:20:40.99,-14:46:51.8,Eri -IC0368,G,04:22:42.72,-12:36:54.6,Eri -IC0369,G,04:23:28.22,-11:47:24.3,Eri -IC0370,G,04:24:01.67,-09:23:41.4,Eri -IC0371,*,04:30:12.59,-00:33:39.6,Eri -IC0372,G,04:30:04.24,-05:00:35.9,Eri -IC0373,G,04:30:42.76,-04:52:12.9,Eri -IC0374,G,04:32:32.75,+16:38:03.0,Tau -IC0375,G,04:31:03.14,-12:58:26.3,Eri -IC0376,G,04:31:13.79,-12:26:00.1,Eri -IC0377,G,04:31:16.54,-12:27:18.3,Eri -IC0378,G,04:31:27.92,-12:17:59.1,Eri -IC0379,G,04:31:50.93,-07:14:18.1,Eri -IC0380,G,04:31:41.32,-12:55:37.3,Eri -IC0381,Dup,04:44:28.50,+75:38:23.1,Cam -IC0382,G,04:37:55.55,-09:31:09.6,Eri -IC0383,G,04:38:58.03,+09:53:33.1,Tau -IC0384,G,04:39:18.28,-07:50:20.8,Eri -IC0385,G,04:39:31.46,-07:05:50.9,Eri -IC0386,Dup,04:39:58.58,-09:27:22.4,Eri -IC0387,G,04:41:44.21,-07:05:10.3,Eri -IC0388,GPair,04:41:53.33,-07:18:19.9,Eri -IC0388 NED01,G,04:41:52.42,-07:18:16.7,Eri -IC0388 NED02,G,04:41:54.32,-07:18:23.3,Eri -IC0389,G,04:41:59.62,-07:18:41.3,Eri -IC0390,G,04:42:03.87,-07:12:23.2,Eri -IC0391,G,04:57:21.13,+78:11:19.8,Cam -IC0392,G,04:46:25.76,+03:30:20.0,Ori -IC0393,G,04:47:51.80,-15:31:30.7,Eri -IC0394,Other,04:48:50.71,-06:16:48.7,Eri -IC0395,Dup,04:49:34.03,+00:15:10.3,Ori -IC0396,G,04:57:58.99,+68:19:24.2,Cam -IC0397,Other,05:01:06.63,+40:25:29.6,Aur -IC0398,G,04:58:12.56,-07:46:48.9,Eri -IC0399,G,05:01:44.01,-04:17:19.5,Eri -IC0400,G,05:03:45.64,-15:49:08.7,Lep -IC0401,G,05:04:19.64,-10:04:35.5,Eri -IC0402,G,05:06:14.83,-09:06:26.7,Eri -IC0403,Other,05:15:15.69,+39:58:19.6,Aur -IC0404,G,05:13:19.60,+09:45:17.7,Ori -IC0405,Neb,05:16:29.48,+34:21:22.2,Aur -IC0406,Other,05:17:49.00,+39:53:09.7,Aur -IC0407,G,05:17:42.61,-15:31:24.4,Lep -IC0408,G,05:19:44.86,-25:03:51.6,Lep -IC0409,GPair,05:19:33.55,+03:19:06.2,Ori -IC0410,Neb,05:22:42.00,+33:22:00.0,Aur -IC0411,G,05:20:18.61,-25:19:28.2,Lep -IC0412,G,05:21:56.70,+03:29:11.0,Ori -IC0413,G,05:21:58.78,+03:28:55.8,Ori -IC0414,G,05:21:54.96,+03:20:31.3,Ori -IC0415,G,05:21:21.63,-15:32:33.3,Lep -IC0416,G,05:23:56.38,-17:15:37.5,Lep -IC0417,HII,05:28:06.00,+34:25:26.2,Aur -IC0418,PN,05:27:28.19,-12:41:50.2,Lep -IC0419,Other,05:30:52.02,+30:07:04.5,Aur -IC0420,Neb,05:32:09.50,-04:30:17.1,Ori -IC0421,G,05:32:08.58,-07:55:06.0,Ori -IC0422,G,05:32:18.55,-17:13:25.9,Lep -IC0423,Neb,05:33:22.01,-00:36:52.2,Ori -IC0424,Neb,05:33:37.24,-00:24:47.3,Ori -IC0425,Other,05:37:09.92,+32:25:46.8,Aur -IC0426,Neb,05:36:31.38,-00:17:53.9,Ori -IC0427,Neb,05:36:18.02,-06:36:59.2,Ori -IC0428,Neb,05:36:24.21,-06:27:05.6,Ori -IC0429,Neb,05:38:18.86,-07:02:25.8,Ori -IC0430,Neb,05:38:35.97,-07:04:59.2,Ori -IC0431,RfN,05:40:14.03,-01:27:46.1,Ori -IC0432,RfN,05:40:55.98,-01:30:25.2,Ori -IC0433,G,05:40:31.35,-11:39:56.1,Lep -IC0434,HII,05:41:00.88,-02:27:13.6,Ori -IC0435,RfN,05:43:00.57,-02:18:45.4,Ori -IC0436,Other,05:53:40.08,+38:37:29.2,Aur -IC0437,G,05:51:37.40,-12:33:53.9,Lep -IC0438,G,05:53:00.08,-17:52:33.9,Lep -IC0439,Other,05:56:39.51,+32:01:21.7,Aur -IC0440,G,06:19:13.31,+80:04:06.5,Cam -IC0441,G,06:02:42.63,-12:29:56.9,Lep -IC0442,G,06:36:11.89,+82:58:05.7,Cam -IC0443,SNR,06:16:37.41,+22:31:54.0,Gem -IC0444,RfN,06:18:34.00,+23:18:48.0,Gem -IC0445,G,06:37:21.25,+67:51:35.5,Cam -IC0446,Cl+N,06:31:06.18,+10:27:33.4,Mon -IC0447,HII,06:31:00.32,+09:53:50.8,Mon -IC0448,HII,06:32:45.34,+07:23:19.1,Mon -IC0449,G,06:45:41.11,+71:20:37.8,Cam -IC0450,G,06:52:12.25,+74:25:37.5,Cam -IC0451,G,06:52:52.01,+74:28:50.6,Cam -IC0452,Dup,06:48:39.11,-16:54:05.8,CMa -IC0453,*,06:49:11.45,-16:54:24.3,CMa -IC0454,G,06:51:06.30,+12:55:19.3,Gem -IC0455,G,07:34:57.68,+85:32:13.9,Cep -IC0456,G,07:00:17.54,-30:09:49.6,CMa -IC0457,Dup,07:09:28.40,+50:09:09.1,Lyn -IC0458,G,07:10:34.15,+50:07:08.1,Lyn -IC0459,G,07:10:38.69,+50:10:38.4,Lyn -IC0460,G,07:10:44.27,+50:12:08.8,Lyn -IC0461,G,07:10:45.04,+50:04:53.1,Lyn -IC0462,*,07:10:55.81,+50:10:51.0,Lyn -IC0463,G,07:11:00.89,+50:07:03.7,Lyn -IC0464,G,07:11:04.75,+50:08:12.8,Lyn -IC0465,Dup,07:11:33.65,+50:14:53.7,Lyn -IC0466,HII,07:08:38.80,-04:19:04.8,Mon -IC0467,G,07:30:18.39,+79:52:21.1,Cam -IC0468,Other,07:17:19.00,-13:13:07.3,CMa -IC0469,G,07:55:59.08,+85:09:32.1,Cep -IC0470,**,07:23:31.50,+46:04:43.2,Lyn -IC0471,G,07:43:36.45,+49:40:03.2,Lyn -IC0472,G,07:43:50.34,+49:36:51.1,Lyn -IC0473,Other,07:42:24.62,+09:15:19.0,CMi -IC0474,G,07:46:07.31,+26:30:18.4,Gem -IC0475,G,07:47:09.23,+30:29:19.7,Gem -IC0476,G,07:47:16.33,+26:57:03.5,Gem -IC0477,G,07:52:06.93,+23:28:58.9,Gem -IC0478,G,07:53:41.57,+26:29:33.7,Gem -IC0479,G,07:54:22.22,+27:00:31.8,Gem -IC0480,G,07:55:23.19,+26:44:36.0,Gem -IC0481,G,07:59:02.86,+24:09:38.3,Gem -IC0482,G,07:59:47.28,+25:21:25.1,Gem -IC0483,**,07:59:52.38,+25:55:31.2,Gem -IC0484,G,08:00:01.06,+26:39:57.1,Gem -IC0485,G,08:00:19.77,+26:42:05.2,Gem -IC0486,G,08:00:20.98,+26:36:48.7,Gem -IC0487,Dup,07:59:07.14,-00:38:16.5,Mon -IC0488,Other,08:00:49.51,+25:54:06.7,Cnc -IC0489,*,08:01:37.74,+25:59:47.0,Cnc -IC0490,G,08:03:20.12,+25:48:41.1,Cnc -IC0491,G,08:03:55.00,+26:31:14.2,Cnc -IC0492,G,08:05:38.66,+26:10:05.5,Cnc -IC0493,G,08:07:27.59,+25:08:02.5,Cnc -IC0494,G,08:06:24.12,+01:02:09.8,CMi -IC0495,G,08:08:19.42,+09:00:50.0,Cnc -IC0496,G,08:09:44.16,+25:52:53.8,Cnc -IC0497,G,08:10:06.07,+24:55:19.5,Cnc -IC0498,G,08:09:30.27,+05:16:50.5,CMi -IC0499,G,08:45:17.30,+85:44:24.1,Cam -IC0500,G,08:12:39.60,-16:03:02.9,Pup -IC0501,G,08:18:47.59,+24:32:14.7,Cnc -IC0502,G,08:22:03.61,+08:45:09.4,Cnc -IC0503,G,08:22:10.68,+03:16:05.0,Hya -IC0504,G,08:22:41.20,+04:15:44.7,Hya -IC0505,G,08:23:21.67,+04:22:20.9,Hya -IC0506,G,08:23:30.73,+04:17:58.2,Hya -IC0507,Dup,08:25:02.00,-00:35:29.0,Hya -IC0508,G,08:28:22.31,+25:07:29.0,Cnc -IC0509,G,08:32:03.50,+24:00:39.3,Cnc -IC0510,GPair,08:32:10.60,-02:09:44.0,Hya -IC0510 NED01,G,08:32:10.19,-02:09:43.6,Hya -IC0510 NED02,G,08:32:10.93,-02:09:44.6,Hya -IC0511,G,08:40:50.45,+73:29:12.0,Cam -IC0512,G,09:03:49.83,+85:30:06.1,Cam -IC0513,G,08:33:05.06,-12:21:20.0,Hya -IC0514,G,08:35:22.28,-02:02:49.4,Hya -IC0515,G,08:35:31.29,-01:54:04.0,Hya -IC0516,G,08:35:50.77,-01:52:16.4,Hya -IC0517,G,08:36:22.10,-02:03:20.0,Hya -IC0518,Other,08:36:06.95,+00:41:33.3,Hya -IC0519,G,08:40:34.41,+02:36:40.8,Hya -IC0520,G,08:53:42.26,+73:29:27.4,Cam -IC0521,G,08:46:43.96,+02:32:14.7,Hya -IC0522,G,08:54:34.91,+57:10:00.2,UMa -IC0523,G,08:53:11.32,+09:08:53.4,Cnc -IC0524,G,08:58:12.83,-19:11:30.9,Hya -IC0525,G,09:01:22.47,-01:51:14.3,Hya -IC0526,G,09:02:40.78,+10:50:29.9,Cnc -IC0527,G,09:09:41.78,+37:36:05.7,Lyn -IC0528,G,09:09:22.55,+15:47:46.2,Cnc -IC0529,G,09:18:32.80,+73:45:33.6,Cam -IC0530,G,09:15:16.97,+11:53:08.5,Cnc -IC0531,G,09:17:50.81,-00:16:42.5,Hya -IC0532,Other,09:19:03.76,-16:45:17.8,Hya -IC0533,G,09:20:23.40,-03:59:31.3,Hya -IC0534,G,09:21:15.45,+03:09:04.1,Hya -IC0535,G,09:22:16.22,-01:02:25.2,Hya -IC0536,G,09:24:40.10,+25:06:36.7,Leo -IC0537,G,09:25:22.61,-12:23:30.4,Hya -IC0538,Dup,09:27:18.51,+23:01:12.4,Leo -IC0539,G,09:29:08.24,-02:32:56.9,Hya -IC0540,G,09:30:10.33,+07:54:09.9,Leo -IC0541,Other,09:30:30.79,-04:15:13.2,Hya -IC0542,G,09:31:06.22,-13:10:52.9,Hya -IC0543,Other,09:31:09.10,-14:46:21.0,Hya -IC0544,G,09:35:53.38,+24:53:41.5,Leo -IC0545,G,09:36:05.36,+24:56:56.2,Leo -IC0546,G,09:34:50.24,-16:23:03.9,Hya -IC0547,Dup,09:36:05.79,-12:26:12.2,Hya -IC0548,G,09:38:19.30,+09:26:45.5,Leo -IC0549,G,09:40:43.23,+03:57:35.1,Hya -IC0550,G,09:40:28.58,-06:56:45.8,Hya -IC0551,G,09:41:00.11,+06:56:09.9,Leo -IC0552,G,09:41:16.57,+10:38:49.2,Leo -IC0553,G,09:40:45.13,-05:26:07.3,Hya -IC0554,G,09:41:56.89,+12:17:46.6,Leo -IC0555,Dup,09:41:56.89,+12:17:46.6,Leo -IC0556,Dup,09:43:40.38,+11:03:39.1,Leo -IC0557,G,09:44:02.40,+10:59:17.2,Leo -IC0558,G,09:45:00.35,+29:27:08.3,Leo -IC0559,G,09:44:43.89,+09:36:54.0,Leo -IC0560,G,09:45:53.44,-00:16:05.9,Sex -IC0561,G,09:45:58.81,+03:08:42.7,Sex -IC0562,G,09:46:03.90,-03:58:16.2,Sex -IC0563,G,09:46:20.35,+03:02:44.1,Sex -IC0564,G,09:46:21.08,+03:04:16.9,Sex -IC0565,GPair,09:47:50.76,+15:51:09.2,Leo -IC0565 NED01,G,09:47:49.67,+15:51:07.3,Leo -IC0565 NED02,G,09:47:50.53,+15:51:06.8,Leo -IC0566,G,09:49:56.39,-00:13:52.9,Sex -IC0567,*,09:50:33.78,+12:47:05.0,Leo -IC0568,G,09:51:08.33,+15:43:49.6,Leo -IC0569,G,09:51:28.15,+10:55:12.2,Leo -IC0570,G,09:51:50.98,+15:45:20.7,Leo -IC0571,G,09:52:31.57,+15:46:31.6,Leo -IC0572,G,09:52:32.81,+15:49:36.7,Leo -IC0573,Dup,09:53:36.17,-12:28:55.6,Hya -IC0574,G,09:54:27.03,-06:57:12.2,Sex -IC0575,G,09:54:32.93,-06:51:27.2,Sex -IC0576,G,09:55:07.04,+11:02:22.4,Leo -IC0577,G,09:56:03.97,+10:29:55.9,Leo -IC0578,G,09:56:16.15,+10:29:09.9,Leo -IC0579,G,09:56:39.42,-13:46:29.6,Hya -IC0580,Dup,09:57:56.70,+10:25:56.8,Leo -IC0581,G,09:58:11.58,+15:56:49.1,Leo -IC0582,G,09:59:00.23,+17:49:01.7,Leo -IC0583,G,09:59:05.08,+17:49:17.2,Leo -IC0584,G,09:59:05.13,+10:21:40.1,Leo -IC0585,G,09:59:44.12,+12:59:18.9,Leo -IC0586,G,09:59:50.30,-06:55:22.2,Sex -IC0587,G,10:03:05.17,-02:24:00.1,Sex -IC0588,G,10:02:07.04,+03:03:27.7,Sex -IC0589,G,10:04:23.90,-05:40:44.1,Sex -IC0590,GPair,10:05:50.23,+00:37:58.6,Sex -IC0590 NED01,G,10:05:49.83,+00:38:00.1,Sex -IC0590 NED02,G,10:05:50.67,+00:37:54.5,Sex -IC0591,G,10:07:27.69,+12:16:28.2,Leo -IC0592,G,10:07:58.75,-02:29:50.1,Sex -IC0593,G,10:08:18.01,-02:31:36.5,Sex -IC0594,G,10:08:32.00,-00:40:00.7,Sex -IC0595,G,10:09:38.12,+11:00:00.9,Leo -IC0596,G,10:10:31.35,+10:02:32.6,Leo -IC0597,G,10:10:11.96,-06:53:57.3,Sex -IC0598,G,10:12:48.57,+43:08:43.9,UMa -IC0599,G,10:13:12.51,-05:37:44.4,Sex -IC0600,G,10:17:10.91,-03:29:52.0,Sex -IC0601,G,10:18:15.29,+07:02:19.8,Leo -IC0602,G,10:18:19.73,+07:02:57.5,Leo -IC0603,G,10:19:25.05,-05:39:22.2,Sex -IC0604,Dup,10:23:44.66,+57:01:36.7,UMa -IC0605,G,10:22:24.13,+01:11:53.7,Sex -IC0606,Dup,10:23:32.62,+10:57:35.0,Leo -IC0607,G,10:24:08.58,+16:44:30.8,Leo -IC0608,G,10:24:21.13,-06:02:21.5,Sex -IC0609,G,10:25:35.43,-02:12:54.8,Sex -IC0610,G,10:26:28.37,+20:13:41.5,Leo -IC0611,Dup,10:26:28.37,+20:13:41.5,Leo -IC0612,G,10:27:05.86,+11:03:17.6,Leo -IC0613,G,10:27:07.79,+11:00:38.6,Leo -IC0614,G,10:26:51.85,-03:27:53.1,Sex -IC0615,G,10:27:21.98,+11:04:47.9,Leo -IC0616,G,10:32:47.56,+15:51:39.0,Leo -IC0617,Dup,10:32:43.81,-12:38:14.3,Hya -IC0618,Dup,10:32:45.40,-12:43:02.7,Hya -IC0619,G,10:33:49.98,+12:52:42.1,Leo -IC0620,G,10:33:33.43,+11:52:17.0,Leo -IC0621,G,10:33:21.04,+02:36:58.1,Sex -IC0622,Dup,10:34:42.80,+11:11:50.4,Leo -IC0623,G,10:35:21.01,+03:33:30.2,Sex -IC0624,G,10:36:15.18,-08:20:02.2,Sex -IC0625,G,10:42:38.05,-23:56:08.2,Hya -IC0626,G,10:36:57.10,-07:01:25.9,Sex -IC0627,G,10:37:19.89,-03:21:28.1,Sex -IC0628,G,10:37:36.19,+05:36:13.3,Sex -IC0629,Dup,10:37:02.52,-27:33:54.2,Hya -IC0630,G,10:38:33.61,-07:10:13.9,Sex -IC0631,G,10:38:58.90,-07:03:08.6,Sex -IC0632,G,10:39:11.79,-00:24:34.1,Sex -IC0633,G,10:39:24.38,-00:23:21.5,Sex -IC0634,G,10:40:54.89,+05:59:30.6,Sex -IC0635,G,10:41:45.30,+15:38:36.0,Leo -IC0636,G,10:41:50.59,+04:19:50.8,Sex -IC0637,G,10:42:21.92,+15:21:34.6,Leo -IC0638,G,10:43:47.98,+15:53:42.5,Leo -IC0639,G,10:45:52.02,+16:55:49.6,Leo -IC0640,Other,10:46:50.45,+34:46:03.5,LMi -IC0641,Other,10:47:49.41,+34:40:22.2,LMi -IC0642,G,10:48:08.13,+18:11:19.3,Leo -IC0643,G,10:49:27.18,+12:12:03.6,Leo -IC0644,Dup,10:51:31.43,+55:23:27.5,UMa -IC0645,G,10:50:09.35,-06:02:34.4,Sex -IC0646,G,10:51:35.18,+55:27:57.0,UMa -IC0647,G,10:50:34.49,-12:51:17.3,Hya -IC0648,G,10:51:00.32,+12:17:14.6,Leo -IC0649,GPair,10:50:52.20,+01:09:50.0,Sex -IC0649 NED01,G,10:50:52.09,+01:09:55.2,Sex -IC0649 NED02,G,10:50:52.20,+01:09:42.0,Sex -IC0650,G,10:50:40.55,-13:26:31.4,Hya -IC0651,G,10:50:58.41,-02:09:01.3,Sex -IC0652,Dup,10:50:57.63,-12:26:54.7,Hya -IC0653,G,10:52:06.78,-00:33:38.8,Leo -IC0654,G,10:53:50.39,-11:43:32.1,Crt -IC0655,G,10:54:22.24,-00:21:53.9,Leo -IC0656,Other,10:55:08.14,+17:36:46.4,Leo -IC0657,G,10:57:53.57,-04:54:17.8,Leo -IC0658,G,10:58:16.26,+08:14:30.0,Leo -IC0659,G,10:58:03.86,-06:15:37.9,Leo -IC0660,G,10:58:26.66,+01:22:58.5,Leo -IC0661,G,10:58:51.50,+01:39:02.3,Leo -IC0662,G,10:59:20.55,+01:35:55.8,Leo -IC0663,G,11:00:37.27,+10:26:13.9,Leo -IC0664,G,11:00:45.35,+10:33:11.0,Leo -IC0665,G,11:00:29.93,-13:52:00.5,Crt -IC0666,G,11:01:14.81,+10:28:52.0,Leo -IC0667,G,11:06:36.55,+15:05:19.4,Leo -IC0668,G,11:06:39.56,+15:02:27.3,Leo -IC0669,G,11:07:16.57,+06:18:08.9,Leo -IC0670,G,11:07:28.81,+06:42:51.3,Leo -IC0671,G,11:07:31.60,+00:46:59.2,Leo -IC0672,G,11:08:03.23,-12:29:03.0,Crt -IC0673,G,11:09:25.31,-00:05:51.8,Leo -IC0674,G,11:11:06.36,+43:37:58.8,UMa -IC0675,**,11:10:57.36,+03:35:39.2,Leo -IC0676,G,11:12:39.82,+09:03:21.0,Leo -IC0677,G,11:13:56.50,+12:18:03.9,Leo -IC0678,G,11:14:06.38,+06:34:37.9,Leo -IC0679,G,11:16:36.61,-13:58:19.5,Crt -IC0680,G,11:17:54.70,-01:56:47.2,Leo -IC0681,G,11:18:31.93,-12:08:24.8,Crt -IC0682,Dup,11:22:14.74,+20:12:30.7,Leo -IC0683,G,11:21:31.78,+02:45:06.5,Leo -IC0684,Dup,11:21:32.87,+02:48:37.6,Leo -IC0685,G,11:22:06.52,+17:45:12.3,Leo -IC0686,G,11:23:05.27,+05:38:40.8,Leo -IC0687,G,11:24:17.33,+47:50:51.1,UMa -IC0688,G,11:23:40.24,-09:47:44.0,Crt -IC0689,Dup,11:23:38.43,-13:49:52.0,Crt -IC0690,G,11:24:20.61,-08:20:31.1,Crt -IC0691,G,11:26:44.32,+59:09:19.5,UMa -IC0692,G,11:25:53.47,+09:59:15.0,Leo -IC0693,G,11:26:48.62,-05:00:14.4,Leo -IC0694,G,11:28:27.30,+58:34:42.5,UMa -IC0695,G,11:27:58.29,-11:42:55.2,Crt -IC0696,G,11:28:39.92,+09:05:55.3,Leo -IC0697,G,11:28:34.45,-01:37:46.5,Leo -IC0698,G,11:29:03.84,+09:06:43.4,Leo -IC0699,G,11:29:06.49,+08:59:18.9,Leo -IC0700,GGroup,11:29:15.48,+20:35:05.7,Leo -IC0700 NED01,G,11:29:14.16,+20:34:52.0,Leo -IC0700 NED02,G,11:29:15.30,+20:34:59.4,Leo -IC0700 NED03,G,11:29:16.36,+20:35:11.7,Leo -IC0700 NED04,G,11:29:16.70,+20:35:16.3,Leo -IC0701,G,11:31:00.68,+20:28:08.2,Leo -IC0702,G,11:30:54.71,-04:55:19.2,Leo -IC0703,Dup,11:30:04.65,-11:32:46.8,Crt -IC0704,Dup,11:30:11.56,-11:32:36.6,Crt -IC0705,G,11:32:56.35,+50:14:30.7,UMa -IC0706,G,11:33:12.62,-13:20:17.0,Crt -IC0707,G,11:33:44.64,+21:22:48.2,Leo -IC0708,G,11:33:59.22,+49:03:43.4,UMa -IC0709,G,11:34:14.54,+49:02:35.4,UMa -IC0710,G,11:34:27.41,+25:52:35.3,Leo -IC0711,G,11:34:46.56,+48:57:22.0,UMa -IC0712,G,11:34:49.31,+49:04:39.7,UMa -IC0713,*,11:34:44.22,+16:50:48.8,Leo -IC0714,Dup,11:36:30.19,-09:50:48.1,Crt -IC0715,GPair,11:36:54.63,-08:22:38.6,Crt -IC0715NW,G,11:36:54.21,-08:22:32.9,Crt -IC0715SE,G,11:36:54.94,-08:22:43.4,Crt -IC0716,G,11:39:03.33,-00:12:21.6,Vir -IC0717,Dup,11:38:51.05,-10:35:01.5,Crt -IC0718,G,11:39:52.77,+08:52:28.3,Vir -IC0719,G,11:40:18.51,+09:00:35.6,Vir -IC0720,GPair,11:42:22.30,+08:46:04.0,Vir -IC0720 NED01,G,11:42:22.30,+08:45:55.7,Vir -IC0720 NED02,G,11:42:22.34,+08:46:11.5,Vir -IC0721,G,11:42:28.88,-08:20:25.2,Crt -IC0722,G,11:42:43.76,+08:58:27.2,Vir -IC0723,G,11:42:57.60,-08:19:57.0,Crt -IC0724,G,11:43:34.67,+08:56:32.9,Vir -IC0725,G,11:43:29.34,-01:40:04.7,Vir -IC0726,G,11:43:45.30,+33:23:31.0,UMa -IC0727,G,11:44:28.60,+10:47:01.7,Leo -IC0728,G,11:44:50.47,-01:36:04.8,Vir -IC0729,G,11:45:18.32,+33:20:08.9,UMa -IC0730,Dup,11:45:35.25,+03:13:54.6,Vir -IC0731,G,11:45:18.08,+49:34:13.5,UMa -IC0732,GPair,11:45:59.60,+20:26:34.0,Leo -IC0732N,G,11:45:59.43,+20:26:49.3,Leo -IC0732S,G,11:45:59.87,+20:26:20.1,Leo -IC0733,G,11:45:58.56,-08:09:21.0,Crt -IC0734,GPair,11:46:03.80,-08:16:04.0,Crt -IC0734 NED01,G,11:46:03.75,-08:16:15.7,Crt -IC0734 NED02,G,11:46:03.88,-08:15:53.9,Crt -IC0735,G,11:48:12.80,+13:12:33.8,Leo -IC0736,G,11:48:20.12,+12:42:59.7,Leo -IC0737,G,11:48:27.52,+12:43:38.6,Leo -IC0738,G,11:48:54.87,-04:40:55.0,Vir -IC0739,G,11:51:31.29,+23:51:46.3,Leo -IC0740,Dup,11:50:38.94,+55:21:13.9,UMa -IC0741,G,11:50:31.77,-04:50:09.2,Vir -IC0742,G,11:51:02.25,+20:47:59.0,Leo -IC0743,G,11:53:22.26,-13:15:53.5,Crt -IC0744,G,11:54:04.75,+23:11:32.0,Leo -IC0745,G,11:54:12.27,+00:08:11.9,Vir -IC0746,G,11:55:35.13,+25:53:22.0,Leo -IC0747,G,11:57:04.90,-08:17:32.4,Vir -IC0748,G,11:57:26.71,+07:27:39.4,Vir -IC0749,G,11:58:34.05,+42:44:02.5,UMa -IC0750,G,11:58:52.20,+42:43:20.9,UMa -IC0751,G,11:58:52.60,+42:34:13.2,UMa -IC0752,G,11:59:15.00,+42:34:00.6,UMa -IC0753,G,11:59:12.88,-00:31:25.8,Vir -IC0754,G,11:59:23.55,-01:39:16.3,Vir -IC0755,Dup,12:01:10.42,+14:06:15.5,Com -IC0756,G,12:02:57.81,+04:50:45.1,Vir -IC0757,Dup,12:04:00.78,+52:35:17.8,UMa -IC0758,G,12:04:11.88,+62:30:19.3,UMa -IC0759,Other,12:05:09.33,+20:15:36.0,Com -IC0760,G,12:05:53.51,-29:17:31.5,Hya -IC0761,G,12:05:53.70,-12:40:24.0,Crv -IC0762,G,12:08:11.96,+25:45:25.7,Com -IC0763,G,12:08:15.31,+25:48:41.4,Com -IC0764,G,12:10:14.19,-29:44:12.5,Hya -IC0765,Other,12:10:30.97,+16:08:06.7,Com -IC0766,G,12:10:53.60,-12:39:18.9,Crv -IC0767,G,12:11:02.73,+12:06:14.4,Vir -IC0768,G,12:11:47.62,+12:08:37.4,Vir -IC0769,G,12:12:32.33,+12:07:25.7,Vir -IC0770,G,12:13:02.33,-04:33:12.1,Vir -IC0771,G,12:15:13.24,+13:11:04.3,Vir -IC0772,G,12:15:15.89,+23:57:29.4,Com -IC0773,G,12:18:08.09,+06:08:22.4,Vir -IC0774,G,12:18:51.26,-06:45:59.6,Vir -IC0775,G,12:18:53.67,+12:54:45.6,Vir -IC0776,G,12:19:02.90,+08:51:22.0,Vir -IC0777,G,12:19:23.76,+28:18:35.8,Com -IC0778,Dup,12:14:22.05,+56:00:41.2,UMa -IC0779,G,12:19:38.73,+29:52:59.5,Com -IC0780,G,12:19:58.38,+25:46:18.1,Com -IC0781,G,12:20:03.30,+14:57:41.5,Com -IC0782,G,12:21:36.97,+05:45:56.7,Vir -IC0783,G,12:21:38.79,+15:44:42.4,Com -IC0784,G,12:22:30.05,-04:39:09.7,Vir -IC0785,G,12:23:02.08,-13:13:25.1,Crv -IC0786,G,12:23:10.95,-13:12:16.8,Crv -IC0787,G,12:25:25.12,+16:07:27.1,Com -IC0788,Dup,12:26:07.15,+16:10:51.6,Com -IC0789,G,12:26:20.52,+07:27:36.6,Vir -IC0790,Dup,12:26:35.50,+09:02:07.7,Vir -IC0791,G,12:26:59.48,+22:38:22.5,Com -IC0792,G,12:27:08.77,+16:19:31.4,Com -IC0793,Dup,12:28:15.93,+09:26:10.3,Vir -IC0794,G,12:28:08.61,+12:05:35.9,Vir -IC0795,G,12:28:31.34,+23:18:17.8,Com -IC0796,G,12:29:26.35,+16:24:17.2,Com -IC0797,G,12:31:54.76,+15:07:26.2,Com -IC0798,G,12:32:33.41,+15:24:55.4,Com -IC0799,Dup,12:33:49.90,-07:22:32.0,Vir -IC0800,G,12:33:56.66,+15:21:17.4,Com -IC0801,G,12:33:44.95,+52:15:17.3,CVn -IC0802,*,12:35:57.63,+74:18:04.8,Dra -IC0803,GGroup,12:39:37.01,+16:35:16.9,Com -IC0803 NED01,G,12:39:35.86,+16:35:16.2,Com -IC0803 NED02,G,12:39:37.51,+16:35:18.7,Com -IC0804,G,12:41:15.97,-05:00:32.8,Vir -IC0805,Dup,12:41:25.45,+13:43:46.3,Com -IC0806,G,12:42:08.43,-17:20:57.7,Crv -IC0807,G,12:42:12.49,-17:24:12.8,Crv -IC0808,Other,12:41:54.55,+19:55:55.6,Com -IC0809,G,12:42:08.66,+11:45:15.4,Vir -IC0810,G,12:42:09.08,+12:35:48.5,Vir -IC0811,Dup,12:44:47.05,-10:11:52.2,Vir -IC0812,G,12:44:50.86,-04:26:04.6,Vir -IC0813,G,12:45:11.84,+23:02:10.1,Com -IC0814,G,12:45:34.14,-08:05:31.9,Vir -IC0815,G,12:46:22.67,+11:52:35.8,Vir -IC0816,G,12:46:46.34,+09:51:02.0,Vir -IC0817,G,12:46:56.80,+09:51:25.6,Vir -IC0818,G,12:46:44.55,+29:44:07.0,Com -IC0819,Dup,12:46:10.11,+30:43:54.9,Com -IC0820,Dup,12:46:11.24,+30:43:21.9,Com -IC0821,G,12:47:26.16,+29:47:16.0,Com -IC0822,G,12:47:45.57,+30:04:37.9,Com -IC0823,*,12:47:50.86,+27:12:33.0,Com -IC0824,Dup,12:49:41.85,-04:34:47.0,Vir -IC0825,G,12:50:19.17,-05:21:46.8,Vir -IC0826,G,12:51:19.93,+31:03:34.7,Com -IC0827,G,12:51:55.06,+16:16:58.3,Com -IC0828,G,12:52:15.63,-08:07:58.3,Vir -IC0829,G,12:52:27.43,-15:31:06.7,Crv -IC0830,G,12:51:16.52,+53:41:46.7,UMa -IC0831,G,12:52:44.07,+26:28:13.5,Com -IC0832,G,12:53:59.11,+26:26:38.6,Com -IC0833,G,12:56:38.20,-06:44:00.3,Vir -IC0834,G,12:56:18.60,+26:21:32.1,Com -IC0835,G,12:56:52.28,+26:29:15.8,Com -IC0836,G,12:55:54.02,+63:36:44.4,Dra -IC0837,G,12:57:31.21,+26:30:43.8,Com -IC0838,G,12:58:13.57,+26:25:36.8,Com -IC0839,G,12:58:15.05,+28:07:33.3,Com -IC0840,G,12:58:42.03,+10:36:59.4,Vir -IC0841,G,12:59:47.34,+21:48:47.6,Com -IC0842,G,13:00:39.55,+29:01:09.9,Com -IC0843,Dup,13:01:43.37,+29:02:40.8,Com -IC0844,G,13:03:18.22,-30:31:15.9,Cen -IC0845,G,13:04:57.42,+12:04:44.6,Vir -IC0846,G,13:05:21.11,+23:05:44.0,Com -IC0847,Dup,13:05:32.15,+53:41:06.5,UMa -IC0848,G,13:07:01.56,+16:00:25.6,Com -IC0849,G,13:07:38.69,-00:56:32.9,Vir -IC0850,G,13:07:50.23,-00:52:06.4,Vir -IC0851,G,13:08:34.34,+21:02:59.2,Com -IC0852,G,13:07:36.76,+60:09:25.9,UMa -IC0853,G,13:08:41.73,+52:46:27.4,UMa -IC0854,G,13:09:49.97,+24:34:39.2,Com -IC0855,G,13:10:36.90,-04:29:04.3,Vir -IC0856,G,13:10:41.61,+20:32:12.4,Com -IC0857,G,13:13:50.17,+17:04:34.5,Com -IC0858,G,13:14:51.94,+17:13:36.4,Com -IC0859,G,13:14:57.28,+17:13:30.6,Com -IC0860,G,13:15:03.53,+24:37:07.9,Com -IC0861,G,13:15:07.42,+34:19:43.6,CVn -IC0862,G,13:16:15.38,+20:02:51.6,Com -IC0863,G,13:17:12.40,-17:15:16.1,Vir -IC0864,G,13:17:08.49,+20:41:30.2,Com -IC0865,G,13:17:35.49,-05:50:02.0,Vir -IC0866,G,13:17:16.75,+20:41:27.6,Com -IC0867,G,13:17:19.79,+20:38:17.1,Com -IC0868,G,13:17:28.54,+20:36:44.4,Com -IC0869,GPair,13:17:30.76,+20:40:51.9,Com -IC0869 NED01,G,13:17:29.96,+20:41:03.1,Com -IC0869 NED02,G,13:17:31.64,+20:40:39.3,Com -IC0870,G,13:17:30.90,+20:36:00.4,Com -IC0871,G,13:17:58.66,+04:24:12.3,Vir -IC0872,G,13:17:01.60,+06:21:25.5,Vir -IC0873,G,13:18:16.28,+04:27:51.5,Vir -IC0874,G,13:19:00.52,-27:37:42.8,Hya -IC0875,G,13:17:07.57,+57:32:22.0,UMa -IC0876,G,13:18:34.58,+04:29:10.7,Vir -IC0877,Other,13:17:57.37,+06:04:55.4,Vir -IC0878,Other,13:18:00.36,+06:07:13.5,Vir -IC0879,G,13:19:40.57,-27:25:44.3,Hya -IC0880,Other,13:18:07.36,+06:06:43.6,Vir -IC0881,G,13:19:56.35,+15:51:01.9,Com -IC0882,G,13:20:06.94,+15:53:53.3,Com -IC0883,G,13:20:35.34,+34:08:22.2,CVn -IC0884,Other,13:21:54.91,-12:43:47.0,Vir -IC0885,G,13:22:30.92,+21:18:59.1,Com -IC0886,G,13:23:57.49,-04:23:43.6,Vir -IC0887,Other,13:24:11.94,-12:27:37.5,Vir -IC0888,Dup,13:24:51.41,+13:44:16.3,Vir -IC0889,G,13:26:37.54,+11:52:10.2,Vir -IC0890,G,13:28:25.59,-16:05:32.4,Vir -IC0891,G,13:29:59.92,+00:18:18.4,Vir -IC0892,G,13:31:45.88,-02:42:47.0,Vir -IC0893,G,13:31:47.38,-02:36:41.8,Vir -IC0894,G,13:32:04.82,+17:02:56.3,Com -IC0895,Dup,13:42:08.34,+35:39:15.2,CVn -IC0896,G,13:34:10.22,+04:52:06.5,Vir -IC0897,G,13:34:19.45,+17:50:53.0,Com -IC0898,G,13:34:09.41,+13:16:51.0,Vir -IC0899,G,13:34:59.46,-08:05:29.9,Vir -IC0900,G,13:34:43.01,+09:20:12.7,Vir -IC0901,G,13:35:42.40,+13:19:51.3,Vir -IC0902,G,13:36:01.22,+49:57:39.0,UMa -IC0903,G,13:38:26.07,-00:13:39.5,Vir -IC0904,G,13:38:32.21,+00:32:24.7,Vir -IC0905,G,13:40:02.93,+23:08:34.7,Boo -IC0906,G,13:40:09.99,+23:20:27.9,Boo -IC0907,G,13:39:23.01,+51:03:03.8,UMa -IC0908,G,13:41:18.94,-04:20:39.1,Vir -IC0909,G,13:40:51.16,+24:28:23.8,Boo -IC0910,G,13:41:07.86,+23:16:55.4,Boo -IC0911,G,13:41:25.37,+23:14:50.9,Boo -IC0912,G,13:41:28.86,+23:14:43.6,Boo -IC0913,G,13:41:29.66,+23:10:01.0,Boo -IC0914,G,13:41:40.61,+23:11:20.5,Boo -IC0915,G,13:43:27.35,-17:19:58.1,Vir -IC0916,G,13:42:38.15,+24:27:54.2,Boo -IC0917,*,13:42:31.25,+55:38:12.9,UMa -IC0918,G,13:42:37.81,+55:31:46.4,UMa -IC0919,G,13:42:47.50,+55:31:17.0,UMa -IC0920,G,13:45:24.67,-12:34:27.0,Vir -IC0921,G,13:43:08.00,+55:39:04.4,UMa -IC0922,G,13:42:56.36,+55:36:14.0,UMa -IC0923,G,13:43:14.19,+55:36:11.3,UMa -IC0924,G,13:45:37.59,-12:27:18.3,Vir -IC0925,G,13:43:16.10,+55:36:56.9,UMa -IC0926,G,13:43:39.27,+55:37:53.5,UMa -IC0927,G,13:45:52.39,-12:27:52.2,Vir -IC0928,G,13:43:48.00,+55:34:04.3,UMa -IC0929,G,13:43:45.02,+55:38:01.6,UMa -IC0930,G,13:43:45.55,+55:38:47.5,UMa -IC0931,G,13:43:49.19,+55:37:26.3,UMa -IC0932,G,13:43:51.20,+55:38:48.3,UMa -IC0933,G,13:45:16.16,+23:13:08.2,Boo -IC0934,G,13:43:52.39,+55:39:24.5,UMa -IC0935,Dup,13:43:52.39,+55:39:24.5,UMa -IC0936,G,13:44:08.55,+55:42:22.0,UMa -IC0937,G,13:44:28.93,+55:37:48.7,UMa -IC0938,G,13:44:31.25,+55:37:38.6,UMa -IC0939,G,13:47:43.13,+03:24:41.3,Vir -IC0940,G,13:47:57.71,+03:26:58.7,Vir -IC0941,G,13:48:35.64,+24:00:54.3,Boo -IC0942,G,13:47:41.14,+56:37:17.7,UMa -IC0943,G,13:50:32.13,+03:11:39.0,Vir -IC0944,G,13:51:30.87,+14:05:32.0,Boo -IC0945,G,13:47:07.80,+72:04:13.4,UMi -IC0946,G,13:52:08.36,+14:06:58.4,Boo -IC0947,G,13:52:35.90,+00:49:06.1,Vir -IC0948,G,13:52:26.71,+14:05:28.6,Boo -IC0949,G,13:52:16.76,+22:31:17.7,Boo -IC0950,GPair,13:52:26.20,+14:29:23.0,Boo -IC0950 NED01,G,13:52:25.64,+14:29:19.4,Boo -IC0950 NED02,G,13:52:26.73,+14:29:27.2,Boo -IC0951,G,13:51:47.21,+50:58:42.0,UMa -IC0952,G,13:53:41.92,+03:22:38.6,Vir -IC0953,Other,13:54:57.22,-30:16:59.9,Cen -IC0954,G,13:49:56.90,+71:09:52.4,UMi -IC0955,Other,13:55:43.32,-30:15:40.3,Cen -IC0956,GPair,13:54:40.68,+20:43:17.3,Boo -IC0956 NED01,G,13:54:40.28,+20:43:11.3,Boo -IC0956 NED02,G,13:54:40.56,+20:43:01.2,Boo -IC0957,Other,13:56:08.36,-30:14:15.5,Cen -IC0958,Dup,13:55:38.75,+04:59:06.2,Vir -IC0959,G,13:56:03.40,+13:30:21.0,Boo -IC0960,GPair,13:55:59.57,+17:30:21.0,Boo -IC0960A,G,13:55:59.09,+17:29:57.2,Boo -IC0960B,G,13:56:00.12,+17:30:41.6,Boo -IC0961,G,13:55:46.79,+25:50:25.3,Boo -IC0962,G,13:57:13.20,+12:01:16.8,Boo -IC0963,G,13:57:25.02,+17:24:27.9,Boo -IC0964,G,13:57:41.35,+17:30:31.4,Boo -IC0965,G,13:57:47.50,+17:30:37.9,Boo -IC0966,G,13:58:14.00,+05:24:29.8,Vir -IC0967,G,13:58:22.95,+14:27:26.1,Boo -IC0968,GPair,14:00:37.22,-02:54:27.3,Vir -IC0968 NED01,G,14:00:36.61,-02:54:32.8,Vir -IC0968 NED02,G,14:00:37.96,-02:54:22.7,Vir -IC0969,G,14:01:46.20,-04:10:49.4,Vir -IC0970,G,14:02:34.21,+14:33:09.2,Boo -IC0971,G,14:03:52.79,-10:08:26.1,Vir -IC0972,PN,14:04:25.91,-17:13:41.5,Vir -IC0973,Dup,14:06:29.50,-05:28:53.3,Vir -IC0974,*,14:06:34.36,-05:29:32.7,Vir -IC0975,G,14:07:08.84,+15:19:05.1,Boo -IC0976,G,14:08:43.29,-01:09:41.9,Vir -IC0977,G,14:08:42.05,-03:00:08.8,Vir -IC0978,G,14:08:58.10,-02:58:25.6,Vir -IC0979,G,14:09:32.37,+14:49:54.6,Boo -IC0980,G,14:10:22.41,-07:20:33.3,Vir -IC0981,G,14:10:28.14,-04:10:17.2,Vir -IC0982,G,14:09:59.09,+17:41:46.0,Boo -IC0983,G,14:10:04.37,+17:44:01.8,Boo -IC0984,G,14:10:07.76,+18:21:52.8,Boo -IC0985,G,14:11:32.87,-03:13:11.1,Vir -IC0986,G,14:11:26.23,+01:17:11.5,Vir -IC0987,G,14:11:31.88,+19:10:20.2,Boo -IC0988,G,14:14:32.06,+03:11:24.9,Vir -IC0989,G,14:14:51.34,+03:07:51.2,Vir -IC0990,G,14:15:49.19,+39:47:52.5,Boo -IC0991,G,14:17:48.58,-13:52:22.6,Vir -IC0992,G,14:18:14.91,+00:53:27.9,Vir -IC0993,G,14:18:18.62,+11:12:59.0,Boo -IC0994,G,14:18:22.63,+11:11:42.6,Boo -IC0995,G,14:16:31.11,+57:48:36.4,UMa -IC0996,G,14:17:22.07,+57:37:47.5,UMa -IC0997,GPair,14:19:59.25,-04:27:04.5,Vir -IC0997 NED01,G,14:19:59.25,-04:27:04.3,Vir -IC0997 NED02,G,14:19:59.75,-04:27:08.3,Vir -IC0998,G,14:20:19.29,-04:24:59.2,Vir -IC0999,G,14:19:32.67,+17:52:31.1,Boo -IC1000,G,14:19:40.31,+17:51:16.9,Boo -IC1001,G,14:20:39.67,+05:25:38.5,Vir -IC1002,G,14:20:42.28,+05:29:08.8,Vir -IC1003,G,14:21:29.76,+05:04:23.5,Vir -IC1004,G,14:21:02.28,+17:42:02.1,Boo -IC1005,Dup,14:19:26.70,+71:35:17.6,UMi -IC1006,G,14:22:59.10,+23:47:39.6,Boo -IC1007,G,14:24:36.59,+04:33:33.0,Vir -IC1008,GPair,14:23:42.59,+28:20:52.3,Boo -IC1008 NED01,G,14:23:42.26,+28:20:45.9,Boo -IC1008 NED02,G,14:23:43.15,+28:20:50.7,Boo -IC1009,G,14:26:17.60,+12:21:10.5,Boo -IC1010,G,14:27:20.36,+01:01:33.1,Vir -IC1011,G,14:28:04.53,+01:00:22.7,Vir -IC1012,G,14:27:09.50,+30:56:53.6,Boo -IC1013,G,14:27:58.86,+25:51:58.8,Boo -IC1014,G,14:28:18.43,+13:46:48.8,Boo -IC1015,GTrpl,14:28:19.15,+15:25:12.4,Boo -IC1015 NED01,G,14:28:18.88,+15:24:58.9,Boo -IC1015 NED02,G,14:28:19.71,+15:25:19.0,Boo -IC1015 NED03,G,14:28:19.26,+15:25:16.2,Boo -IC1016,Dup,14:27:32.37,+04:49:17.8,Vir -IC1017,G,14:28:07.23,+25:52:07.6,Boo -IC1018,G,14:28:12.80,+25:49:49.9,Boo -IC1019,G,14:28:13.48,+25:56:50.7,Boo -IC1020,G,14:28:49.49,+26:01:56.2,Boo -IC1021,G,14:29:17.13,+20:39:16.3,Boo -IC1022,G,14:30:01.84,+03:46:22.3,Vir -IC1023,OCl,14:32:25.14,-35:48:12.9,Cen -IC1024,G,14:31:27.20,+03:00:32.7,Vir -IC1025,G,14:31:28.28,+07:03:50.1,Vir -IC1026,Dup,14:30:10.42,+31:12:55.8,Boo -IC1027,G,14:29:48.50,+53:57:54.1,Boo -IC1028,G,14:33:16.43,+41:39:01.4,Boo -IC1029,G,14:32:27.26,+49:54:16.6,Boo -IC1030,Dup,14:32:38.34,+31:40:12.6,Boo -IC1031,G,14:34:23.98,+48:02:14.8,Boo -IC1032,G,14:34:39.48,+47:58:04.7,Boo -IC1033,G,14:34:41.75,+47:56:16.1,Boo -IC1034,G,14:37:13.73,+14:39:54.5,Boo -IC1035,G,14:38:10.21,+09:20:09.7,Boo -IC1036,G,14:38:22.79,+18:06:40.4,Boo -IC1037,G,14:38:25.37,+18:11:02.5,Boo -IC1038,G,14:39:27.43,+11:55:43.1,Boo -IC1039,G,14:40:29.40,+03:25:57.9,Vir -IC1040,G,14:40:22.73,+09:28:34.1,Boo -IC1041,G,14:40:37.90,+03:22:37.3,Vir -IC1042,G,14:40:39.02,+03:28:11.0,Vir -IC1043,G,14:40:43.35,+03:22:26.5,Vir -IC1044,G,14:41:29.02,+09:25:51.3,Boo -IC1045,Dup,14:40:09.21,+42:46:46.4,Boo -IC1046,G,14:37:53.42,+69:00:52.1,UMi -IC1047,G,14:42:19.97,+19:11:30.6,Boo -IC1048,G,14:42:58.00,+04:53:22.1,Vir -IC1049,G,14:39:33.12,+62:00:10.6,Dra -IC1050,G,14:44:07.12,+18:00:45.5,Boo -IC1051,G,14:44:11.59,+19:01:12.8,Boo -IC1052,G,14:44:14.11,+20:36:50.4,Boo -IC1053,G,14:45:43.23,+16:56:48.9,Boo -IC1054,G,14:46:31.24,+01:16:29.1,Vir -IC1055,G,14:47:25.69,-13:42:58.1,Lib -IC1056,G,14:45:49.05,+50:23:38.6,Boo -IC1057,Dup,14:45:49.05,+50:23:38.6,Boo -IC1058,G,14:49:12.39,+17:01:15.4,Boo -IC1059,G,14:50:42.56,-00:52:32.9,Lib -IC1060,G,14:51:47.35,-07:13:57.5,Lib -IC1061,G,14:51:14.24,+18:45:27.2,Boo -IC1062,G,14:51:17.66,+18:41:13.1,Boo -IC1063,G,14:52:11.02,+04:40:55.4,Vir -IC1064,NonEx,,, -IC1065,G,14:49:21.57,+63:16:14.0,Dra -IC1066,G,14:53:02.86,+03:17:45.7,Vir -IC1067,G,14:53:05.25,+03:19:54.4,Vir -IC1068,G,14:53:32.92,+03:04:38.3,Vir -IC1069,G,14:50:46.52,+54:24:40.2,Boo -IC1070,G,14:53:51.28,+03:29:04.8,Vir -IC1071,G,14:54:12.50,+04:45:00.1,Vir -IC1072,G,14:54:13.14,+04:50:29.6,Vir -IC1073,G,14:54:14.35,+04:47:39.8,Vir -IC1074,G,14:51:57.32,+51:15:53.6,Boo -IC1075,G,14:54:49.25,+18:06:21.5,Boo -IC1076,G,14:54:59.62,+18:02:14.4,Boo -IC1077,G,14:57:21.72,-19:12:49.3,Lib -IC1078,G,14:56:29.02,+09:21:16.3,Boo -IC1079,G,14:56:36.16,+09:22:11.1,Boo -IC1080,G,14:57:59.82,-06:43:23.9,Lib -IC1081,G,14:58:55.06,-19:14:20.7,Lib -IC1082,G,14:58:52.49,+07:00:26.3,Vir -IC1083,G,14:55:33.41,+68:24:30.9,UMi -IC1084,G,15:01:14.88,-07:28:29.7,Lib -IC1085,G,15:02:43.37,+17:15:09.1,Boo -IC1086,G,15:03:29.17,+17:06:51.9,Boo -IC1087,G,15:06:43.89,+03:46:36.6,Vir -IC1088,*,15:06:47.42,+03:47:30.7,Vir -IC1089,G,15:07:25.99,+07:07:00.3,Vir -IC1090,Other,15:05:43.15,+42:40:56.5,Boo -IC1091,G,15:08:13.51,-11:08:27.3,Lib -IC1092,G,15:07:36.12,+09:21:29.6,Boo -IC1093,G,15:07:35.64,+14:32:52.8,Boo -IC1094,GTrpl,15:07:42.20,+14:37:30.0,Boo -IC1094 NED01,G,15:07:41.81,+14:37:18.8,Boo -IC1094 NED02,G,15:07:42.19,+14:37:40.7,Boo -IC1094 NED03,G,15:07:42.59,+14:37:36.3,Boo -IC1095,G,15:08:35.05,+13:40:14.2,Boo -IC1096,G,15:08:21.57,+19:11:31.6,Boo -IC1097,G,15:08:31.30,+19:11:03.6,Boo -IC1098,*,15:06:25.24,+55:36:06.0,Dra -IC1099,G,15:06:54.65,+56:30:32.4,Dra -IC1100,Dup,15:06:20.82,+62:58:51.5,Dra -IC1101,G,15:10:56.10,+05:44:41.2,Vir -IC1102,G,15:11:04.95,+04:17:37.9,Vir -IC1103,G,15:11:35.87,+19:12:28.0,Se1 -IC1104,*,15:12:49.88,-05:03:20.2,Lib -IC1105,G,15:13:13.91,+04:17:15.2,Se1 -IC1106,G,15:13:56.28,+04:42:39.5,Se1 -IC1107,G,15:14:08.96,+04:42:51.9,Se1 -IC1108,Dup,15:16:50.00,-45:38:57.5,Lup -IC1109,G,15:17:03.98,+05:15:22.0,Se1 -IC1110,G,15:12:05.07,+67:21:45.3,UMi -IC1111,Dup,15:09:31.56,+54:30:23.4,Boo -IC1112,G,15:17:47.35,+07:13:05.8,Se1 -IC1113,G,15:18:15.11,+12:29:19.2,Se1 -IC1114,*,15:11:25.69,+75:25:36.1,UMi -IC1115,**,15:22:19.02,-04:28:25.9,Lib -IC1116,G,15:21:55.27,+08:25:25.6,Se1 -IC1117,G,15:24:22.90,+15:29:18.9,Se1 -IC1118,G,15:24:59.53,+13:26:42.3,Se1 -IC1119,GPair,15:25:44.28,-03:39:22.7,Se1 -IC1119 NED01,G,15:25:44.15,-03:39:14.2,Se1 -IC1119 NED02,G,15:25:44.62,-03:39:29.0,Se1 -IC1120,G,15:26:11.04,+18:52:20.4,Se1 -IC1121,G,15:27:44.06,+06:48:14.0,Se1 -IC1122,G,15:29:23.07,+07:37:02.7,Se1 -IC1123,*,15:28:54.11,+42:53:54.7,Boo -IC1124,G,15:30:00.87,+23:38:18.3,Se1 -IC1125,G,15:33:05.61,-01:37:41.7,Se1 -IC1126,*,15:35:00.82,+04:59:25.5,Se1 -IC1127,Dup,15:34:57.25,+23:30:11.3,Se1 -IC1128,Dup,15:33:05.61,-01:37:41.7,Se1 -IC1129,G,15:32:00.82,+68:14:46.8,UMi -IC1130,G,15:37:44.03,+17:14:39.9,Se1 -IC1131,G,15:38:51.70,+12:04:50.3,Se1 -IC1132,G,15:40:06.76,+20:40:50.2,Se1 -IC1133,G,15:41:11.99,+15:34:24.3,Se1 -IC1134,G,15:44:58.50,+16:57:43.6,Se1 -IC1135,G,15:45:34.71,+17:42:00.3,Se1 -IC1136,G,15:47:34.39,-01:32:43.0,Se1 -IC1137,G,15:48:32.60,+08:35:16.5,Se1 -IC1138,G,15:48:15.77,+26:12:22.4,CrB -IC1139,G,15:29:26.10,+82:35:01.9,UMi -IC1140,Other,15:49:25.26,+19:06:48.4,Se1 -IC1141,G,15:49:46.94,+12:23:57.6,Se1 -IC1142,G,15:50:25.92,+18:08:22.5,Se1 -IC1143,G,15:30:55.96,+82:27:20.8,UMi -IC1144,G,15:51:21.69,+43:25:03.6,Her -IC1145,G,15:44:08.55,+72:25:52.2,UMi -IC1146,G,15:48:22.08,+69:23:08.1,Dra -IC1147,G,15:50:11.61,+69:33:36.2,Dra -IC1148,Dup,15:57:08.14,+22:24:16.5,Se1 -IC1149,G,15:58:07.98,+12:04:13.0,Se1 -IC1150,Other,15:58:18.56,+15:52:28.3,Se1 -IC1151,G,15:58:32.34,+17:26:29.3,Se1 -IC1152,G,15:56:43.32,+48:05:42.0,Her -IC1153,G,15:57:03.01,+48:10:06.2,Her -IC1154,G,15:52:28.61,+70:22:30.3,UMi -IC1155,G,16:00:35.76,+15:41:08.3,Se1 -IC1156,G,16:00:37.36,+19:43:23.6,Her -IC1157,G,16:00:56.26,+15:31:35.2,Se1 -IC1158,G,16:01:34.08,+01:42:28.2,Se1 -IC1159,G,16:01:01.46,+15:25:11.9,Se1 -IC1160,G,16:01:02.51,+15:29:40.9,Se1 -IC1161,G,16:01:16.82,+15:38:43.5,Se1 -IC1162,G,16:01:16.33,+17:40:40.2,Her -IC1163,G,16:01:30.55,+15:30:14.1,Se1 -IC1164,*,15:55:02.71,+70:35:13.1,UMi -IC1165,GPair,16:02:08.20,+15:41:38.0,Her -IC1165 NED01,G,16:02:08.03,+15:41:47.6,Her -IC1165 NED02,G,16:02:08.64,+15:41:35.4,Her -IC1166,GPair,16:02:08.90,+26:19:38.0,CrB -IC1166 NED01,G,16:02:08.92,+26:19:45.6,CrB -IC1166 NED02,G,16:02:08.83,+26:19:31.2,CrB -IC1167,G,16:03:52.86,+14:56:47.2,Se1 -IC1168,G,16:03:55.69,+14:54:08.7,Se1 -IC1169,G,16:04:13.43,+13:44:38.5,Se1 -IC1170,G,16:04:31.69,+17:43:17.3,Her -IC1171,*,16:04:51.84,+17:58:41.6,Her -IC1172,Dup,16:04:59.68,+17:52:13.3,Her -IC1173,G,16:05:12.56,+17:25:22.3,Her -IC1174,G,16:05:26.82,+15:01:31.2,Se1 -IC1175,**,16:05:22.62,+18:09:47.2,Her -IC1176,Dup,16:05:31.28,+17:57:49.1,Her -IC1177,G,16:05:19.79,+18:18:55.3,Her -IC1178,G,16:05:33.12,+17:36:05.2,Her -IC1179,Dup,16:05:22.22,+17:45:15.1,Her -IC1180,*,16:05:30.11,+18:08:58.8,Her -IC1181,G,16:05:33.85,+17:35:37.3,Her -IC1182,G,16:05:36.80,+17:48:07.7,Her -IC1183,Dup,16:05:38.15,+17:46:04.4,Her -IC1184,Other,16:05:42.80,+17:47:24.0,Her -IC1185,G,16:05:44.69,+17:43:01.3,Her -IC1186,G,16:05:44.22,+17:21:43.8,Her -IC1187,G,15:59:10.18,+70:33:25.1,UMi -IC1188,GPair,16:06:07.68,+17:27:36.0,Her -IC1188A,G,16:06:07.30,+17:27:38.9,Her -IC1188B,G,16:06:08.28,+17:27:41.9,Her -IC1189,G,16:06:14.83,+18:10:58.4,Her -IC1190,G,16:05:52.41,+18:13:13.9,Her -IC1191,GPair,16:06:28.75,+18:16:04.4,Her -IC1191 NED01,G,16:06:28.99,+18:16:04.3,Her -IC1191 NED02,G,16:06:29.70,+18:16:04.3,Her -IC1192,G,16:06:33.13,+17:46:32.3,Her -IC1193,G,16:06:32.20,+17:42:49.9,Her -IC1194,G,16:06:39.35,+17:45:40.3,Her -IC1195,G,16:06:40.88,+17:11:30.5,Her -IC1196,G,16:07:58.37,+10:46:46.7,Se1 -IC1197,G,16:08:17.27,+07:32:18.6,Se1 -IC1198,G,16:08:36.37,+12:19:51.4,Se1 -IC1199,G,16:10:34.35,+10:02:25.3,Se1 -IC1200,Dup,16:04:29.21,+69:39:56.8,UMi -IC1201,G,16:05:41.65,+69:35:37.7,Dra -IC1202,Dup,16:12:56.86,+09:52:01.6,Her -IC1203,*Ass,16:15:16.00,-22:22:14.1,Sco -IC1204,G,16:07:15.48,+69:55:53.3,UMi -IC1205,G,16:14:15.92,+09:32:13.9,Her -IC1206,G,16:15:13.07,+11:17:50.6,Her -IC1207,*Ass,16:19:26.72,-29:39:04.0,Sco -IC1208,G,16:15:47.89,+36:31:38.4,CrB -IC1209,G,16:18:39.62,+15:33:30.1,Her -IC1210,G,16:14:30.15,+62:32:12.1,Dra -IC1211,G,16:16:51.98,+53:00:21.7,Dra -IC1212,G,16:15:30.75,+64:13:29.3,Dra -IC1213,Dup,16:22:10.30,-01:30:53.5,Se1 -IC1214,G,16:16:11.70,+65:58:07.6,Dra -IC1215,G,16:15:35.13,+68:23:51.6,Dra -IC1216,G,16:15:55.38,+68:20:59.6,Dra -IC1217,Other,16:16:04.10,+69:40:35.4,Dra -IC1218,G,16:16:37.10,+68:12:09.5,Dra -IC1219,G,16:24:27.45,+19:28:57.3,Her -IC1220,G,16:29:38.30,+08:27:02.6,Her -IC1221,G,16:34:41.63,+46:23:31.4,Her -IC1222,G,16:35:09.20,+46:12:50.1,Her -IC1223,G,16:35:42.46,+49:13:13.9,Her -IC1224,G,16:42:56.29,+19:15:15.7,Her -IC1225,G,16:36:52.52,+67:37:45.9,Dra -IC1226,G,16:41:06.57,+46:00:14.6,Her -IC1227,Dup,16:40:07.90,+58:37:02.5,Dra -IC1228,G,16:42:06.48,+65:35:07.8,Dra -IC1229,G,16:44:58.82,+51:18:29.1,Dra -IC1230,GPair,16:45:01.48,+51:15:34.5,Her -IC1231,G,16:46:59.02,+58:25:23.4,Dra -IC1232,Dup,16:41:06.57,+46:00:14.6,Her -IC1233,Dup,16:48:20.24,+62:58:35.1,Dra -IC1234,*,16:52:50.91,+56:52:40.0,Dra -IC1235,G,16:52:03.63,+63:06:56.9,Dra -IC1236,G,16:58:29.60,+20:02:29.3,Her -IC1237,G,16:56:16.05,+55:01:35.3,Dra -IC1238,**,17:00:30.15,+23:04:36.3,Her -IC1239,Dup,17:00:45.09,+23:02:38.4,Her -IC1240,Other,17:00:58.87,+61:03:01.6,Dra -IC1241,G,17:01:28.22,+63:41:28.0,Dra -IC1242,G,17:08:42.88,+04:02:59.6,Oph -IC1243,Other,17:10:24.55,+10:45:59.8,Oph -IC1244,G,17:10:33.71,+36:18:11.9,Her -IC1245,G,17:12:36.60,+38:01:13.6,Her -IC1246,*,17:14:12.28,+20:14:14.3,Her -IC1247,*,17:16:22.14,-12:46:51.2,Oph -IC1248,G,17:11:40.18,+59:59:44.2,Dra -IC1249,G,17:14:55.12,+35:31:12.4,Her -IC1250,G,17:14:29.17,+57:25:00.4,Dra -IC1251,G,17:10:12.90,+72:24:37.8,Dra -IC1252,G,17:15:50.38,+57:22:00.5,Dra -IC1253,Dup,17:19:54.67,+16:39:38.5,Her -IC1254,G,17:11:33.41,+72:24:07.2,Dra -IC1255,G,17:23:05.39,+12:41:43.6,Oph -IC1256,G,17:23:47.31,+26:29:11.5,Her -IC1257,GCl,17:27:08.38,-07:05:35.1,Oph -IC1258,G,17:27:17.38,+58:29:07.7,Dra -IC1259,GPair,17:27:25.80,+58:31:00.0,Dra -IC1259 NED01,G,17:27:24.71,+58:31:00.9,Dra -IC1259 NED02,G,17:27:26.78,+58:30:59.6,Dra -IC1260,G,17:27:31.72,+58:28:33.1,Dra -IC1261,GPair,17:23:23.36,+71:15:49.1,Dra -IC1261 NED01,G,17:23:26.12,+71:15:46.1,Dra -IC1261 NED02,G,17:23:20.88,+71:15:51.7,Dra -IC1262,G,17:33:02.02,+43:45:34.6,Her -IC1263,G,17:33:07.20,+43:49:19.5,Her -IC1264,G,17:33:16.84,+43:37:45.2,Her -IC1265,G,17:36:39.43,+42:05:18.3,Her -IC1266,PN,17:45:35.30,-46:05:23.5,Ara -IC1267,G,17:38:45.89,+59:22:23.3,Dra -IC1268,G,17:50:39.29,+17:12:34.0,Her -IC1269,G,17:52:06.00,+21:34:10.7,Her -IC1270,*,17:47:57.00,+62:13:24.4,Dra -IC1271,Neb,18:05:13.11,-24:24:37.9,Sgr -IC1272,Other,18:04:55.75,+25:07:45.1,Her -IC1273,**,18:05:02.75,+25:07:55.6,Her -IC1274,HII,18:09:51.03,-23:38:53.6,Sgr -IC1275,Neb,18:10:07.19,-23:45:40.4,Sgr -IC1276,GCl,18:10:44.27,-07:12:27.3,Se2 -IC1277,G,18:10:27.29,+31:00:11.4,Her -IC1278,Other,18:10:41.65,+31:08:59.7,Her -IC1279,G,18:11:15.38,+36:00:28.0,Her -IC1280,Dup,18:12:18.42,+25:39:44.5,Her -IC1281,GPair,18:11:38.10,+35:59:32.0,Her -IC1281 NED01,G,18:11:38.02,+35:59:14.7,Her -IC1281 NED02,G,18:11:38.16,+35:59:50.1,Her -IC1282,Other,18:14:05.25,+21:06:07.9,Her -IC1283,HII,18:17:16.85,-19:45:44.0,Sgr -IC1284,Neb,18:17:39.63,-19:40:19.3,Sgr -IC1285,Other,18:16:10.17,+25:06:01.2,Her -IC1286,G,18:16:14.27,+55:35:27.6,Dra -IC1287,RfN,18:31:25.69,-10:47:44.9,Sct -IC1288,G,18:29:22.46,+39:42:48.1,Lyr -IC1289,G,18:30:02.29,+39:57:50.5,Lyr -IC1290,*Ass,18:38:35.31,-24:05:48.6,Sgr -IC1291,G,18:33:52.57,+49:16:43.0,Dra -IC1292,Other,18:44:40.45,-27:48:58.6,Sgr -IC1293,Other,18:41:36.65,+56:19:04.0,Dra -IC1294,Other,18:49:50.45,+40:12:33.8,Lyr -IC1295,PN,18:54:37.13,-08:49:37.3,Sct -IC1296,G,18:53:18.83,+33:03:59.7,Lyr -IC1297,PN,19:17:23.40,-39:36:47.0,CrA -IC1298,Other,19:18:35.69,-01:35:46.3,Aql -IC1299,OCl,19:22:42.03,+20:44:22.4,Vul -IC1300,Dup,19:24:03.17,+53:37:29.2,Cyg -IC1301,G,19:26:31.98,+50:07:31.0,Cyg -IC1302,G,19:30:52.89,+35:47:06.9,Cyg -IC1303,G,19:31:30.06,+35:52:35.8,Cyg -IC1304,*Ass,19:35:34.49,+41:06:42.6,Cyg -IC1305,Other,19:39:17.10,+20:11:39.4,Vul -IC1306,*Ass,19:41:41.19,+37:41:06.7,Cyg -IC1307,*Ass,19:42:32.10,+27:45:09.5,Vul -IC1308,HII,19:45:05.24,-14:43:12.9,Sgr -IC1309,G,20:03:01.52,-17:13:55.5,Sgr -IC1310,Cl+N,20:10:00.98,+34:58:08.0,Cyg -IC1311,OCl,20:10:47.82,+41:10:26.2,Cyg -IC1312,Other,20:16:51.50,+18:02:45.5,Sge -IC1313,G,20:18:43.66,-16:56:45.5,Cap -IC1314,*Ass,20:17:50.00,+25:05:23.2,Vul -IC1315,Other,20:17:21.95,+30:41:20.7,Cyg -IC1316,Other,20:22:25.84,+06:30:06.1,Aql -IC1317,G,20:23:15.59,+00:39:53.0,Aql -IC1318,*,20:22:13.69,+40:15:24.1,Cyg -IC1319,G,20:26:01.93,-18:30:14.7,Cap -IC1320,G,20:26:25.66,+02:54:35.0,Del -IC1321,G,20:28:11.05,-18:17:29.4,Cap -IC1322,G,20:30:08.48,-15:13:40.2,Cap -IC1323,**,20:30:29.00,-15:10:55.0,Cap -IC1324,G,20:32:12.31,-09:03:22.0,Cap -IC1325,Dup,20:32:50.22,+09:55:35.1,Del -IC1326,NonEx,,, -IC1327,G,20:35:41.27,-00:00:20.8,Aql -IC1328,G,20:41:57.04,-19:37:59.0,Cap -IC1329,Other,20:43:42.11,+15:35:50.7,Del -IC1330,G,20:46:14.94,-14:01:23.6,Aqr -IC1331,G,20:47:48.77,-09:59:45.4,Aqr -IC1332,G,20:51:51.39,-13:42:41.5,Aqr -IC1333,G,20:52:17.20,-16:17:08.7,Cap -IC1334,Dup,20:52:17.20,-16:17:08.7,Cap -IC1335,G,20:53:06.12,-16:20:07.6,Cap -IC1336,G,20:55:04.92,-18:02:19.4,Cap -IC1337,G,20:56:52.70,-16:35:09.0,Cap -IC1338,G,20:56:57.84,-16:29:33.4,Cap -IC1339,G,20:57:55.52,-17:56:34.2,Cap -IC1340,SNR,20:56:08.25,+31:02:52.3,Cyg -IC1341,G,21:00:16.68,-13:58:34.8,Aqr -IC1342,G,21:00:25.44,-14:29:45.1,Aqr -IC1343,G,21:01:00.66,-15:24:13.3,Cap -IC1344,G,21:01:16.45,-13:22:49.0,Aqr -IC1345,G,21:01:22.20,-13:23:51.4,Aqr -IC1346,G,21:01:37.02,-13:57:38.5,Aqr -IC1347,G,21:01:44.39,-13:18:47.9,Aqr -IC1348,G,21:01:44.11,-13:21:28.8,Aqr -IC1349,G,21:01:50.45,-13:15:55.8,Aqr -IC1350,G,21:01:52.29,-13:51:09.6,Aqr -IC1351,G,21:01:52.44,-13:12:06.7,Aqr -IC1352,G,21:01:54.90,-13:23:02.7,Aqr -IC1353,G,21:01:56.32,-13:16:22.2,Aqr -IC1354,Dup,21:01:52.29,-13:51:09.6,Aqr -IC1355,G,21:01:58.39,-13:10:22.8,Aqr -IC1356,G,21:02:53.00,-15:48:41.7,Cap -IC1357,G,21:05:57.31,-10:42:58.5,Aqr -IC1358,G,21:06:29.41,-16:12:16.0,Cap -IC1359,G,21:08:43.02,+12:29:03.4,Del -IC1360,G,21:10:50.32,+05:04:16.9,Equ -IC1361,G,21:11:29.14,+05:03:15.6,Equ -IC1362,G,21:11:52.65,+02:19:44.3,Aqr -IC1363,OCl,21:10:40.42,+46:52:12.2,Cyg -IC1364,G,21:13:24.68,+02:46:11.0,Equ -IC1365,GGroup,21:13:55.91,+02:33:55.4,Equ -IC1365 NED01,G,21:13:55.92,+02:33:55.4,Equ -IC1365 NED02,G,21:13:54.66,+02:33:49.7,Equ -IC1366,G,21:14:08.02,+01:46:34.0,Aqr -IC1367,G,21:14:09.70,+02:59:37.7,Equ -IC1368,G,21:14:12.59,+02:10:40.8,Aqr -IC1369,OCl,21:12:09.04,+47:46:06.6,Cyg -IC1370,G,21:15:14.27,+02:11:31.3,Aqr -IC1371,G,21:20:15.65,-04:52:35.5,Aqr -IC1372,G,21:20:17.09,-05:36:16.5,Aqr -IC1373,G,21:20:37.25,+01:05:32.7,Aqr -IC1374,G,21:21:02.65,+01:42:46.7,Aqr -IC1375,G,21:20:59.79,+03:59:07.5,Equ -IC1376,Other,21:24:41.10,-05:44:32.8,Aqr -IC1377,G,21:25:26.63,+04:18:51.2,Equ -IC1378,Other,21:22:51.71,+55:27:51.5,Cep -IC1379,G,21:26:01.26,+03:05:50.6,Equ -IC1380,G,21:27:11.01,+02:43:03.6,Peg -IC1381,G,21:27:33.70,-01:11:19.0,Aqr -IC1382,Dup,21:22:07.56,+18:39:56.4,Peg -IC1383,G,21:27:39.64,-01:06:07.8,Aqr -IC1384,G,21:27:53.07,-01:22:06.8,Aqr -IC1385,G,21:28:51.19,-01:04:12.4,Aqr -IC1386,G,21:29:37.43,-21:11:44.5,Cap -IC1387,G,21:29:34.47,-01:21:03.3,Aqr -IC1388,G,21:29:52.18,-00:37:52.6,Aqr -IC1389,G,21:32:07.84,-18:01:06.1,Cap -IC1390,G,21:32:24.78,-01:51:45.1,Aqr -IC1391,G,21:35:00.40,-00:30:41.2,Aqr -IC1392,G,21:35:32.69,+35:23:54.0,Cyg -IC1393,G,21:40:14.25,-22:24:41.0,Cap -IC1394,G,21:40:13.03,+14:37:58.9,Peg -IC1395,G,21:41:41.41,+04:06:16.3,Peg -IC1396,Cl+N,21:38:57.62,+57:29:20.6,Cep -IC1397,G,21:44:02.31,-04:53:05.3,Aqr -IC1398,G,21:45:51.43,+09:28:31.2,Peg -IC1399,G,21:46:08.91,+04:24:08.0,Peg -IC1400,*Ass,21:44:16.28,+52:58:01.3,Cyg -IC1401,G,21:46:59.49,+01:42:45.9,Aqr -IC1402,*Ass,21:44:58.97,+53:15:45.0,Cyg -IC1403,G,21:50:29.08,-02:42:58.2,Aqr -IC1404,G,21:50:56.38,-09:15:59.7,Cap -IC1405,G,21:50:49.84,+02:01:14.9,Aqr -IC1406,G,21:51:04.87,+01:59:13.4,Aqr -IC1407,G,21:52:23.44,+03:25:37.8,Peg -IC1408,G,21:53:09.02,-13:20:48.4,Cap -IC1409,G,21:53:19.60,-07:29:59.5,Aqr -IC1410,G,21:56:02.14,-02:54:01.0,Aqr -IC1411,G,21:56:00.59,-01:31:01.4,Aqr -IC1412,G,21:58:18.46,-17:10:34.2,Cap -IC1413,G,21:58:26.61,-03:06:08.7,Aqr -IC1414,G,21:58:18.05,+08:25:25.7,Peg -IC1415,Other,21:58:42.64,+01:21:01.3,Aqr -IC1416,Other,21:58:49.48,+01:27:05.6,Aqr -IC1417,G,22:00:21.61,-13:08:50.5,Aqr -IC1418,G,22:01:59.92,+04:23:03.6,Peg -IC1419,G,22:02:58.84,-09:55:15.5,Aqr -IC1420,GPair,22:02:31.97,+19:45:00.4,Peg -IC1420 NED01,G,22:02:31.30,+19:45:01.3,Peg -IC1420 NED02,G,22:02:32.28,+19:45:00.4,Peg -IC1421,G,22:03:04.03,-09:58:41.4,Aqr -IC1422,G,22:03:00.06,+02:35:56.0,Peg -IC1423,G,22:03:12.69,+04:17:51.3,Peg -IC1424,*,22:03:09.39,+11:11:49.7,Peg -IC1425,G,22:03:24.51,+02:35:41.7,Peg -IC1426,**,22:03:51.28,-09:55:09.5,Aqr -IC1427,G,22:03:35.16,+15:06:24.4,Peg -IC1428,G,22:04:27.67,+02:37:51.1,Peg -IC1429,*,22:07:02.45,+10:06:33.2,Peg -IC1430,G,22:07:29.82,-13:34:52.3,Aqr -IC1431,G,22:07:39.60,-13:30:48.2,Aqr -IC1432,G,22:10:03.97,+03:41:22.5,Peg -IC1433,GTrpl,22:12:10.20,-12:45:55.0,Aqr -IC1433 NED01,G,22:12:10.64,-12:45:56.1,Aqr -IC1433 NED02,G,22:12:09.85,-12:45:56.2,Aqr -IC1433 NED03,G,22:12:09.22,-12:45:47.3,Aqr -IC1434,OCl,22:10:42.16,+52:51:00.3,Lac -IC1435,G,22:13:26.29,-22:05:48.1,Aqr -IC1436,G,22:13:51.44,-10:11:30.4,Aqr -IC1437,G,22:15:44.99,+02:03:57.4,Aqr -IC1438,G,22:16:29.09,-21:25:50.5,Aqr -IC1439,G,22:16:40.14,-21:29:09.4,Aqr -IC1440,G,22:16:33.22,-16:00:59.2,Aqr -IC1441,G,22:15:19.14,+37:18:05.4,Lac -IC1442,OCl,22:16:01.33,+53:59:28.8,Lac -IC1443,G,22:19:03.66,-20:56:23.7,Aqr -IC1444,G,22:22:23.92,+05:08:21.0,Peg -IC1445,G,22:25:30.33,-17:14:35.9,Aqr -IC1446,G,22:29:04.70,-01:11:05.7,Aqr -IC1447,G,22:29:59.80,-05:07:11.6,Aqr -IC1448,Dup,22:34:32.14,-12:56:01.9,Aqr -IC1449,G,22:35:07.00,-08:45:55.2,Aqr -IC1450,**,22:37:57.93,+34:32:07.8,Peg -IC1451,G,22:46:07.46,-10:22:09.9,Aqr -IC1452,Dup,22:45:59.19,+10:52:03.0,Peg -IC1453,G,22:46:54.23,-13:26:58.6,Aqr -IC1454,PN,22:42:25.01,+80:26:32.0,Cep -IC1455,G,22:53:46.06,+01:22:19.1,Psc -IC1456,G,22:55:18.19,-12:43:55.2,Aqr -IC1457,Other,22:55:23.76,-05:33:45.9,Aqr -IC1458,Dup,22:56:41.38,-07:22:44.6,Aqr -IC1459,G,22:57:10.61,-36:27:44.0,Gru -IC1460,G,22:57:04.08,+04:40:37.3,Psc -IC1461,G,22:58:34.30,+15:10:22.0,Peg -IC1462,*,22:58:37.15,+08:26:28.7,Peg -IC1463,**,22:59:20.93,-10:31:51.1,Aqr -IC1464,GPair,23:03:11.60,-08:59:27.0,Aqr -IC1464A,G,23:03:12.08,-08:59:34.7,Aqr -IC1464B,G,23:03:11.04,-08:59:21.0,Aqr -IC1465,Other,23:02:53.20,+16:34:56.9,Peg -IC1466,G,23:03:39.05,-02:46:31.6,Psc -IC1467,G,23:04:49.71,-03:13:47.8,Psc -IC1468,G,23:05:07.57,-03:12:16.2,Psc -IC1469,G,23:06:28.67,-13:32:11.7,Aqr -IC1470,HII,23:05:10.21,+60:14:38.7,Cep -IC1471,G,23:08:44.85,-12:38:22.0,Aqr -IC1472,G,23:09:06.68,+17:15:32.9,Peg -IC1473,G,23:11:05.41,+29:38:36.4,Peg -IC1474,G,23:12:51.27,+05:48:22.9,Psc -IC1475,Other,23:14:02.22,-28:25:20.8,Scl -IC1476,G,23:14:16.35,+30:33:05.3,Peg -IC1477,Dup,23:17:12.00,-06:54:43.2,Aqr -IC1478,Dup,23:18:13.92,+10:17:53.9,Peg -IC1479,G,23:18:46.41,-10:23:57.3,Aqr -IC1480,Dup,23:18:59.24,+11:20:30.2,Peg -IC1481,G,23:19:25.12,+05:54:22.2,Psc -IC1482,G,23:20:49.52,+01:44:20.5,Psc -IC1483,Dup,23:22:33.12,+11:19:43.7,Peg -IC1484,G,23:22:39.94,+11:23:04.1,Peg -IC1485,Dup,23:22:48.23,+11:22:22.3,Peg -IC1486,Dup,23:23:54.02,+09:40:02.9,Peg -IC1487,Dup,23:24:20.09,+14:38:49.7,Peg -IC1488,G,23:25:38.55,+15:21:15.9,Peg -IC1489,G,23:26:32.13,-12:30:59.4,Aqr -IC1490,G,23:59:10.72,-04:07:37.2,Psc -IC1491,G,23:29:24.69,-16:18:59.8,Aqr -IC1492,G,23:30:36.10,-03:02:23.7,Psc -IC1493,G,23:30:27.51,+14:27:31.9,Peg -IC1494,G,23:30:46.14,-12:43:28.3,Aqr -IC1495,G,23:30:47.74,-13:29:07.6,Aqr -IC1496,G,23:30:53.51,-02:56:03.5,Psc -IC1497,Other,23:28:50.09,+11:59:13.2,Peg -IC1498,G,23:31:53.65,-05:00:25.1,Aqr -IC1499,**,23:31:57.03,-13:26:22.9,Aqr -IC1500,G,23:33:09.36,+04:33:09.1,Psc -IC1501,G,23:34:40.06,-03:09:10.2,Psc -IC1502,G,23:36:20.52,+75:38:53.3,Cep -IC1503,G,23:38:27.11,+04:48:05.1,Psc -IC1504,G,23:41:19.49,+04:01:02.9,Psc -IC1505,G,23:41:37.11,-03:33:54.3,Aqr -IC1506,G,23:44:48.41,+04:44:08.4,Psc -IC1507,G,23:45:33.17,+01:41:19.5,Psc -IC1508,G,23:45:55.07,+12:03:42.3,Peg -IC1509,G,23:47:16.68,-15:18:23.1,Aqr -IC1510,GGroup,23:50:32.80,+02:04:24.0,Psc -IC1510 NED01,G,23:50:32.50,+02:04:29.0,Psc -IC1510 NED02,G,23:50:33.04,+02:04:20.5,Psc -IC1511,*,23:51:00.45,+27:03:39.1,Peg -IC1512,*,23:51:01.43,+27:01:37.7,Peg -IC1513,G,23:53:29.38,+11:19:03.4,Peg -IC1514,Dup,23:54:16.56,-13:35:11.2,Aqr -IC1515,G,23:56:03.91,-00:59:18.7,Psc -IC1516,G,23:56:07.09,-00:54:59.5,Psc -IC1517,G,23:56:18.81,-00:18:20.2,Psc -IC1518,G,23:57:06.12,+12:27:53.9,Peg -IC1519,G,23:57:08.37,+12:27:27.2,Peg -IC1520,G,23:57:54.46,-14:02:21.6,Cet -IC1521,G,23:58:59.66,-07:08:48.4,Cet -IC1522,G,23:59:03.45,+01:43:11.6,Psc -IC1523,G,23:59:06.60,+06:52:23.0,Psc -IC1524,Dup,23:59:10.72,-04:07:37.2,Psc -IC1525,G,23:59:15.75,+46:53:22.8,And -IC1526,G,00:01:31.52,+11:20:45.2,Peg -IC1527,G,00:02:21.60,+04:05:23.1,Psc -IC1528,G,00:05:05.37,-07:05:36.3,Cet -IC1529,G,00:05:13.17,-11:30:10.1,Cet -IC1530,Dup,00:07:19.53,+32:36:33.3,And -IC1531,G,00:09:35.58,-32:16:37.2,Scl -IC1532,G,00:09:52.80,-64:22:19.5,Tuc -IC1533,Other,00:10:36.41,-07:24:54.5,Cet -IC1534,G,00:13:45.44,+48:09:04.9,And -IC1535,G,00:13:57.34,+48:09:28.7,And -IC1536,G,00:14:19.01,+48:08:35.8,And -IC1537,Other,00:15:51.27,-39:15:43.5,Scl -IC1538,Other,00:18:01.56,+30:01:45.4,And -IC1539,Dup,00:18:22.54,+30:04:46.5,And -IC1540,G,00:19:48.75,+23:46:21.4,And -IC1541,G,00:20:01.96,+21:59:59.1,And -IC1542,G,00:20:41.19,+22:35:32.8,And -IC1543,G,00:20:55.45,+21:51:57.7,And -IC1544,G,00:21:17.50,+23:05:27.1,And -IC1545,G,00:21:20.91,+21:59:00.4,And -IC1546,Dup,00:21:29.03,+22:30:21.1,And -IC1547,Other,00:21:35.69,+22:30:23.2,And -IC1548,G,00:21:55.16,+22:00:22.7,And -IC1549,G,00:22:49.83,+06:57:51.4,Psc -IC1550,G,00:24:27.65,+38:11:08.3,And -IC1551,G,00:27:35.49,+08:52:39.0,Psc -IC1552,G,00:29:43.71,+21:28:36.6,Psc -IC1553,G,00:32:40.12,-25:36:27.1,Scl -IC1554,G,00:33:07.35,-32:15:30.1,Scl -IC1555,G,00:34:32.64,-30:01:04.3,Scl -IC1556,G,00:35:02.91,-09:22:06.3,Cet -IC1557,G,00:35:34.52,-02:52:35.1,Cet -IC1558,G,00:35:47.07,-25:22:27.9,Scl -IC1559,Dup,00:36:52.33,+23:59:05.9,And -IC1560,Other,00:37:39.23,+02:40:17.6,Cet -IC1561,G,00:38:32.49,-24:20:23.8,Cet -IC1562,G,00:38:33.96,-24:16:26.6,Cet -IC1563,Dup,00:39:00.24,-09:00:52.5,Cet -IC1564,G,00:39:05.13,+06:01:15.8,Psc -IC1565,G,00:39:26.27,+06:44:03.3,Psc -IC1566,G,00:39:33.36,+06:48:54.5,Psc -IC1567,Dup,00:39:26.27,+06:44:03.3,Psc -IC1568,G,00:39:55.96,+06:50:54.9,Psc -IC1569,G,00:40:28.02,+06:43:10.9,Psc -IC1570,G,00:40:34.10,+06:45:09.6,Psc -IC1571,G,00:40:37.85,-00:19:50.4,Cet -IC1572,Other,00:41:12.10,+16:14:15.0,Psc -IC1573,G,00:42:10.46,-23:35:29.7,Cet -IC1574,G,00:43:03.82,-22:14:48.8,Cet -IC1575,G,00:43:33.36,-04:07:04.2,Cet -IC1576,G,00:44:14.15,-25:06:33.4,Scl -IC1577,Dup,00:43:34.47,-08:11:11.4,Cet -IC1578,G,00:44:25.93,-25:04:36.6,Scl -IC1579,G,00:45:32.45,-26:33:55.6,Scl -IC1580,Other,00:46:21.33,+29:56:11.8,And -IC1581,G,00:45:46.32,-25:55:12.6,Scl -IC1582,G,00:46:16.82,-24:16:45.6,Cet -IC1583,G,00:47:10.30,+23:04:25.8,And -IC1584,G,00:47:18.58,+27:49:39.6,And -IC1585,G,00:47:14.30,+23:03:12.8,And -IC1586,G,00:47:56.32,+22:22:22.4,And -IC1587,G,00:48:43.30,-23:33:42.0,Cet -IC1588,G,00:50:57.71,-23:33:28.6,Cet -IC1589,**,00:51:59.36,-34:25:19.4,Scl -IC1590,Cl+N,00:52:50.20,+56:38:34.9,Cas -IC1591,Dup,00:52:06.57,-22:40:48.5,Cet -IC1592,G,00:53:27.04,+05:46:13.4,Psc -IC1593,**,00:54:39.63,+32:31:10.1,Psc -IC1594,G,00:53:45.33,-47:38:50.9,Phe -IC1595,G,00:53:47.05,-45:11:11.9,Phe -IC1596,G,00:54:42.83,+21:31:21.5,Psc -IC1597,G,00:53:32.09,-58:06:25.7,Tuc -IC1598,G,00:54:41.77,+05:46:25.7,Psc -IC1599,G,00:54:32.81,-23:29:41.7,Cet -IC1600,G,00:55:04.24,-23:31:29.6,Cet -IC1601,G,00:55:34.84,-24:09:12.2,Cet -IC1602,G,00:55:51.89,-09:59:08.4,Cet -IC1603,G,00:56:59.68,-45:24:46.6,Phe -IC1604,Other,00:57:58.95,-16:13:48.2,Cet -IC1605,G,00:57:37.64,-48:54:09.6,Phe -IC1606,Other,00:58:22.17,-12:10:42.6,Cet -IC1607,G,00:58:48.86,+00:35:14.1,Cet -IC1608,G,00:59:24.37,-34:19:44.2,Scl -IC1609,G,00:59:46.62,-40:20:00.8,Phe -IC1610,G,01:01:42.58,-15:34:04.0,Cet -IC1611,OCl,00:59:47.90,-72:19:56.4,Tuc -IC1612,OCl,00:59:58.61,-72:22:14.6,Tuc -IC1613,G,01:04:47.79,+02:07:04.0,Cet -IC1614,G,01:05:06.96,+33:11:23.3,Psc -IC1615,G,01:04:07.04,-51:07:59.0,Phe -IC1616,G,01:04:56.18,-27:25:45.7,Scl -IC1617,G,01:04:16.80,-51:01:58.1,Phe -IC1618,G,01:05:55.98,+32:24:43.8,Psc -IC1619,G,01:07:22.44,+33:04:02.2,Psc -IC1620,G,01:07:14.25,+13:57:18.4,Psc -IC1621,G,01:06:22.63,-46:43:31.8,Phe -IC1622,G,01:07:36.67,-17:32:19.1,Cet -IC1623,GPair,01:07:47.18,-17:30:25.3,Cet -IC1623A,G,01:07:46.70,-17:30:26.0,Cet -IC1623B,G,01:07:47.56,-17:30:25.1,Cet -IC1624,OCl,01:05:21.84,-72:02:33.1,Tuc -IC1625,G,01:07:42.61,-46:54:27.3,Phe -IC1626,OCl,01:06:13.43,-73:17:46.0,Tuc -IC1627,G,01:08:10.84,-46:05:38.6,Phe -IC1628,G,01:08:47.53,-28:34:56.4,Scl -IC1629,G,01:09:18.20,+02:34:02.9,Cet -IC1630,G,01:08:16.77,-46:45:14.5,Phe -IC1631,G,01:08:44.87,-46:28:32.9,Phe -IC1632,G,01:10:43.38,+17:40:59.7,Psc -IC1633,G,01:09:55.58,-45:55:52.3,Phe -IC1634,G,01:11:03.79,+17:39:47.0,Psc -IC1635,G,01:11:03.53,+17:39:07.3,Psc -IC1636,G,01:11:34.96,+33:21:55.4,Psc -IC1637,G,01:11:01.14,-30:26:18.7,Scl -IC1638,G,01:12:21.81,+33:21:52.3,Psc -IC1639,G,01:11:46.55,-00:39:51.7,Cet -IC1640,G,01:11:51.30,-00:37:49.3,Cet -IC1641,OCl,01:09:39.19,-71:46:08.3,Tuc -IC1642,NonEx,,, -IC1643,G,01:12:08.63,-00:24:36.7,Cet -IC1644,HII,01:09:13.01,-73:11:38.8,Tuc -IC1645,G,01:12:27.34,+15:45:00.3,Psc -IC1646,G,01:12:43.83,+15:42:28.1,Psc -IC1647,G,01:13:14.61,+38:53:07.0,And -IC1648,G,01:13:42.12,+33:13:05.6,Psc -IC1649,G,01:11:50.94,-55:51:26.4,Phe -IC1650,G,01:12:19.09,-50:24:06.2,Phe -IC1651,Other,01:13:27.56,+02:04:09.0,Cet -IC1652,G,01:14:56.26,+31:56:54.6,Psc -IC1653,Dup,01:15:07.59,+33:22:38.4,Psc -IC1654,G,01:15:11.88,+30:11:41.4,Psc -IC1655,OCl,01:11:54.10,-71:19:50.2,Tuc -IC1656,Dup,01:15:37.63,+33:04:03.8,Psc -IC1657,G,01:14:07.02,-32:39:03.2,Scl -IC1658,Dup,01:15:49.60,+31:04:48.9,Psc -IC1659,G,01:16:06.02,+30:20:56.7,Psc -IC1660,OCl,01:12:37.67,-71:45:42.3,Tuc -IC1661,Dup,01:16:12.40,+33:03:50.8,Psc -IC1662,OCl,01:12:32.75,-73:27:24.3,Tuc -IC1663,Dup,01:14:07.02,-32:39:03.2,Scl -IC1664,Other,01:14:18.51,-69:48:41.5,Tuc -IC1665,Other,01:17:44.91,+34:42:06.0,And -IC1666,G,01:19:53.39,+32:28:02.4,Psc -IC1667,G,01:18:42.35,-17:03:00.8,Cet -IC1668,G,01:18:53.10,+33:10:23.0,Psc -IC1669,G,01:20:06.84,+33:11:05.1,Psc -IC1670,GPair,01:18:50.80,-16:48:10.0,Cet -IC1670A,G,01:18:48.84,-16:48:12.4,Cet -IC1670B,G,01:18:52.85,-16:48:12.4,Cet -IC1671,Dup,01:19:02.34,-17:03:37.4,Cet -IC1672,G,01:20:38.20,+29:41:55.8,Psc -IC1673,G,01:20:46.35,+33:02:41.8,Psc -IC1674,G,01:19:18.44,-50:57:48.9,Phe -IC1675,G,01:20:59.90,+34:14:55.0,And -IC1676,G,01:20:58.59,+30:15:34.1,Psc -IC1677,G,01:21:07.09,+33:12:58.1,Psc -IC1678,G,01:21:02.53,+05:33:37.9,Psc -IC1679,G,01:21:44.60,+33:29:37.0,Psc -IC1680,G,01:21:51.20,+33:16:57.0,Psc -IC1681,G,01:21:21.32,+00:05:25.4,Cet -IC1682,G,01:22:13.30,+33:15:37.0,Psc -IC1683,G,01:22:39.00,+34:26:13.0,And -IC1684,G,01:22:53.20,+33:24:49.0,Psc -IC1685,G,01:23:06.60,+33:11:22.0,Psc -IC1686,Dup,01:23:11.50,+33:27:38.0,Psc -IC1687,G,01:23:19.10,+33:16:31.0,Psc -IC1688,G,01:23:28.10,+33:04:59.0,Psc -IC1689,G,01:23:47.86,+33:03:19.2,Psc -IC1690,G,01:23:49.54,+33:09:22.6,Psc -IC1691,G,01:24:25.80,+33:24:25.0,Psc -IC1692,G,01:24:39.56,+33:14:08.9,Psc -IC1693,G,01:24:02.40,-01:39:25.3,Cet -IC1694,G,01:24:47.80,+01:36:25.8,Cet -IC1695,G,01:25:07.63,+08:41:58.2,Psc -IC1696,G,01:24:52.36,-01:37:01.4,Cet -IC1697,G,01:25:02.94,+00:26:39.7,Cet -IC1698,G,01:25:22.14,+14:50:19.4,Psc -IC1699,Dup,01:25:22.14,+14:50:19.4,Psc -IC1700,Dup,01:25:24.66,+14:51:52.6,Psc -IC1701,G,01:25:50.40,+18:11:04.1,Psc -IC1702,G,01:25:56.29,+16:36:06.5,Psc -IC1703,Dup,01:26:25.15,-01:38:19.4,Cet -IC1704,G,01:27:09.53,+14:46:34.6,Psc -IC1705,G,01:26:44.84,-03:30:05.3,Cet -IC1706,G,01:27:31.02,+14:49:10.4,Psc -IC1707,Other,01:28:00.32,+37:07:01.4,And -IC1708,OCl,01:24:56.08,-71:11:04.0,Hyi -IC1709,Dup,01:27:57.01,-35:43:03.7,Scl -IC1710,Dup,01:30:46.64,+21:26:25.5,Psc -IC1711,G,01:30:55.26,+17:11:18.8,Psc -IC1712,Dup,01:31:20.75,-06:52:05.0,Cet -IC1713,*,01:32:43.80,+35:19:28.1,Tri -IC1714,G,01:32:53.17,-13:01:29.9,Cet -IC1715,G,01:33:34.14,+12:35:07.6,Psc -IC1716,*,01:33:26.85,-12:18:28.6,Cet -IC1717,Other,01:32:30.33,-67:32:12.6,Hyi -IC1718,G,01:38:26.75,+33:21:59.5,Tri -IC1719,G,01:37:35.92,-33:55:26.7,Scl -IC1720,G,01:40:21.59,-28:54:45.9,Scl -IC1721,G,01:41:24.44,+08:31:31.9,Psc -IC1722,G,01:43:02.71,-34:11:15.8,Scl -IC1723,G,01:43:14.18,+08:53:21.7,Psc -IC1724,G,01:43:09.68,-34:14:30.8,Scl -IC1725,G,01:45:11.83,+21:46:35.6,Psc -IC1726,G,01:45:19.67,+04:37:06.7,Psc -IC1727,G,01:47:29.89,+27:20:00.1,Tri -IC1728,G,01:47:44.49,-33:36:05.4,For -IC1729,G,01:47:55.26,-26:53:31.7,For -IC1730,G,01:49:57.94,+22:00:43.6,Ari -IC1731,G,01:50:12.34,+27:11:46.2,Tri -IC1732,G,01:50:47.90,+35:55:57.9,And -IC1733,G,01:50:42.89,+33:04:55.3,Tri -IC1734,G,01:49:17.05,-32:44:33.4,For -IC1735,G,01:50:51.74,+33:05:32.4,Tri -IC1736,G,01:50:53.20,+18:18:10.0,Ari -IC1737,Other,01:51:42.72,+36:15:04.3,And -IC1738,G,01:51:07.90,-09:47:31.3,Cet -IC1739,G,01:50:29.60,-34:03:20.1,For -IC1740,**,01:48:51.56,-30:05:09.5,For -IC1741,G,01:51:56.73,-16:47:16.9,Cet -IC1742,G,01:53:14.23,+22:43:17.0,Ari -IC1743,Dup,01:52:59.68,+12:42:30.5,Ari -IC1744,Dup,01:53:38.84,+19:50:25.5,Ari -IC1745,G,01:52:59.09,-16:40:08.4,Cet -IC1746,G,01:54:24.31,+04:48:14.0,Psc -IC1747,PN,01:57:35.73,+63:19:18.4,Cas -IC1748,G,01:56:08.86,+17:38:28.6,Ari -IC1749,G,01:56:11.10,+06:44:41.8,Psc -IC1750,G,01:56:18.56,+04:04:34.6,Psc -IC1751,Dup,01:56:21.03,+05:37:44.2,Psc -IC1752,G,01:57:15.42,+28:36:48.6,Tri -IC1753,G,01:57:19.31,+28:35:21.3,Tri -IC1754,G,01:56:49.88,+04:01:32.2,Psc -IC1755,G,01:57:09.79,+14:32:59.6,Ari -IC1756,G,01:57:05.34,-00:28:05.7,Cet -IC1757,G,01:57:11.36,-00:28:26.3,Cet -IC1758,G,01:56:52.49,-16:32:31.4,Cet -IC1759,G,01:57:55.36,-32:59:13.4,For -IC1760,G,01:57:24.68,-31:59:18.0,For -IC1761,G,01:58:52.29,+00:34:05.9,Cet -IC1762,G,01:57:48.64,-33:14:23.3,For -IC1763,G,01:59:11.75,-27:48:38.5,For -IC1764,G,02:00:23.37,+24:34:50.0,Ari -IC1765,Dup,02:01:06.61,+31:52:56.9,Tri -IC1766,Dup,02:01:40.00,+31:49:35.4,Tri -IC1767,G,01:59:59.38,-11:04:44.3,Cet -IC1768,G,02:00:49.87,-25:01:36.1,For -IC1769,G,02:00:54.92,-31:55:11.2,For -IC1770,G,02:02:14.39,+09:58:51.4,Psc -IC1771,G,02:02:15.86,+09:58:06.9,Psc -IC1772,G,02:02:42.93,+07:44:43.9,Psc -IC1773,Dup,02:04:02.11,+30:49:58.3,Tri -IC1774,G,02:03:59.00,+15:19:05.0,Ari -IC1775,G,02:05:17.54,+13:30:20.6,Ari -IC1776,G,02:05:15.22,+06:06:24.6,Psc -IC1777,G,02:06:08.70,+15:12:34.1,Ari -IC1778,Dup,02:06:19.40,+09:13:38.7,Psc -IC1779,G,02:06:25.93,+03:42:21.5,Psc -IC1780,G,02:06:51.16,+14:43:18.9,Ari -IC1781,G,02:06:52.79,-00:31:05.1,Cet -IC1782,Dup,02:07:20.05,-25:26:30.8,For -IC1783,G,02:10:06.13,-32:56:23.5,For -IC1784,G,02:16:12.79,+32:38:58.2,Tri -IC1785,G,02:16:21.04,+32:39:59.5,Tri -IC1786,G,02:16:05.63,+05:08:43.7,Cet -IC1787,Dup,02:16:10.44,-11:55:36.2,Cet -IC1788,G,02:15:49.96,-31:12:03.6,For -IC1789,G,02:17:51.18,+32:23:45.7,Tri -IC1790,G,02:17:37.79,+12:30:32.3,Ari -IC1791,G,02:17:41.34,+12:28:14.2,Ari -IC1792,G,02:19:01.09,+34:27:44.6,Tri -IC1793,G,02:21:32.40,+32:32:40.2,Tri -IC1794,G,02:21:30.17,+15:45:41.9,Ari -IC1795,HII,02:26:31.96,+62:02:29.9,Cas -IC1796,G,02:22:47.32,-41:22:15.9,Phe -IC1797,G,02:25:27.89,+20:23:43.2,Ari -IC1798,G,02:26:15.51,+13:25:50.8,Ari -IC1799,G,02:28:45.91,+45:58:14.3,And -IC1800,Other,02:28:31.16,+31:24:34.8,Tri -IC1801,G,02:28:12.75,+19:35:00.0,Ari -IC1802,G,02:29:13.98,+23:04:57.7,Ari -IC1803,G,02:29:49.94,+23:06:30.9,Ari -IC1804,G,02:29:54.42,+23:05:49.5,Ari -IC1805,Cl+N,02:32:41.51,+61:27:24.8,Cas -IC1806,G,02:29:34.95,+22:56:35.6,Ari -IC1807,G,02:30:31.00,+22:56:59.0,Ari -IC1808,Dup,02:30:31.28,-04:12:55.5,Cet -IC1809,G,02:31:40.35,+22:55:01.9,Ari -IC1810,G,02:29:26.83,-43:04:34.8,Eri -IC1811,G,02:30:38.19,-34:15:51.0,For -IC1812,G,02:29:31.78,-42:48:40.9,Eri -IC1813,G,02:30:49.49,-34:13:15.2,For -IC1814,Dup,02:31:05.79,-36:02:04.8,For -IC1815,G,02:34:20.00,+32:25:46.1,Tri -IC1816,G,02:31:51.00,-36:40:19.4,For -IC1817,GPair,02:33:50.20,+11:12:12.0,Ari -IC1817 NED01,G,02:33:49.60,+11:12:11.9,Ari -IC1817 NED02,G,02:33:50.78,+11:12:12.6,Ari -IC1818,G,02:34:07.14,-11:02:26.7,Cet -IC1819,G,02:35:41.83,+04:03:06.5,Cet -IC1820,G,02:35:52.67,+06:02:26.2,Cet -IC1821,GPair,02:36:26.13,+13:46:46.4,Ari -IC1821 NED01,G,02:36:25.41,+13:46:48.6,Ari -IC1821 NED02,G,02:36:26.70,+13:46:44.1,Ari -IC1822,*,02:35:42.35,-08:33:46.1,Cet -IC1823,G,02:38:36.98,+32:04:10.9,Tri -IC1824,Dup,02:42:35.06,+61:35:39.7,Cas -IC1825,G,02:38:55.63,+09:05:50.5,Cet -IC1826,G,02:39:03.55,-27:26:35.3,For -IC1827,G,02:39:46.46,+01:33:29.8,Cet -IC1828,Dup,02:40:28.99,+19:17:49.6,Ari -IC1829,G,02:40:32.79,+14:17:52.6,Ari -IC1830,Dup,02:39:03.55,-27:26:35.3,For -IC1831,Neb,02:43:56.40,+62:24:41.9,Cas -IC1832,G,02:41:57.66,+19:01:48.3,Ari -IC1833,G,02:41:38.70,-28:10:16.6,For -IC1834,G,02:42:48.09,+03:05:03.0,Cet -IC1835,GGroup,02:43:49.15,+14:53:22.5,Ari -IC1836,G,02:43:23.46,+03:06:19.4,Cet -IC1837,Dup,02:43:31.31,+00:18:24.5,Cet -IC1838,G,02:44:42.99,+19:27:18.4,Ari -IC1839,G,02:44:42.91,+15:14:24.3,Ari -IC1840,Dup,02:43:41.98,-15:42:20.1,Cet -IC1841,G,02:45:36.23,+18:55:44.1,Ari -IC1842,G,02:45:23.42,+11:27:29.2,Ari -IC1843,G,02:45:24.62,+02:52:49.9,Cet -IC1844,G,02:45:49.40,+03:13:49.1,Cet -IC1845,**,02:43:56.90,-27:58:08.1,For -IC1846,Dup,02:47:43.58,+13:15:19.2,Ari -IC1847,G,02:47:53.67,+14:30:18.4,Ari -IC1848,Cl+N,02:51:10.59,+60:24:08.9,Cas -IC1849,G,02:47:44.63,+09:21:24.5,Cet -IC1850,Dup,02:48:39.35,+13:15:34.3,Ari -IC1851,*,02:51:45.92,+58:18:51.5,Cas -IC1852,Dup,02:49:00.37,+13:13:25.5,Ari -IC1853,G,02:48:04.27,-13:59:35.2,Eri -IC1854,G,02:49:20.72,+19:18:14.1,Ari -IC1855,G,02:49:04.31,+13:26:34.4,Ari -IC1856,G,02:48:50.80,-00:46:02.6,Cet -IC1857,G,02:49:38.92,+14:37:11.3,Ari -IC1858,G,02:49:08.41,-31:17:22.5,For -IC1859,G,02:49:03.92,-31:10:21.0,For -IC1860,G,02:49:33.71,-31:11:20.8,For -IC1861,G,02:53:06.98,+25:29:25.4,Ari -IC1862,G,02:51:58.80,-33:20:24.6,For -IC1863,G,02:54:50.74,+08:47:03.8,Cet -IC1864,G,02:53:39.31,-34:11:51.5,For -IC1865,G,02:55:20.18,+08:49:41.4,Cet -IC1866,G,02:54:52.99,-15:39:09.2,Eri -IC1867,G,02:55:52.23,+09:18:42.6,Cet -IC1868,G,02:56:05.85,+09:22:43.8,Cet -IC1869,G,02:58:11.69,+05:50:11.7,Cet -IC1870,G,02:57:53.53,-02:20:49.5,Eri -IC1871,HII,02:57:21.79,+60:40:20.5,Cas -IC1872,Other,03:04:34.61,+42:48:38.4,Per -IC1873,G,03:03:52.88,+09:36:47.7,Cet -IC1874,G,03:06:21.98,+36:00:52.3,Per -IC1875,G,03:03:56.64,-39:26:25.5,For -IC1876,G,03:04:32.29,-27:27:37.8,For -IC1877,G,03:03:09.58,-50:30:42.9,Hor -IC1878,G,03:03:40.20,-52:06:28.9,Hor -IC1879,G,03:03:52.45,-52:07:03.9,Hor -IC1880,G,03:06:28.62,-09:43:53.3,Eri -IC1881,Dup,03:09:17.31,+38:38:58.0,Per -IC1882,G,03:07:49.30,+03:08:52.4,Cet -IC1883,Dup,03:09:42.23,+40:53:35.1,Per -IC1884,Dup,03:09:42.74,+40:58:27.2,Per -IC1885,G,03:06:40.43,-32:51:49.1,For -IC1886,G,03:08:03.24,-04:23:59.4,Eri -IC1887,Dup,03:10:12.92,+40:45:56.4,Per -IC1888,Dup,03:10:56.16,+41:08:13.6,Per -IC1889,Dup,03:11:03.11,+40:37:19.6,Per -IC1890,G,03:09:58.43,+19:12:29.0,Ari -IC1891,G,03:10:12.02,+19:36:23.6,Ari -IC1892,G,03:08:27.23,-23:03:20.8,Eri -IC1893,G,03:10:16.57,+19:37:00.7,Ari -IC1894,G,03:10:25.47,+19:36:23.8,Ari -IC1895,G,03:09:36.22,-25:15:12.9,For -IC1896,G,03:07:52.52,-54:12:52.1,Hor -IC1897,G,03:10:45.93,-10:47:45.8,Eri -IC1898,G,03:10:19.79,-22:24:17.5,Eri -IC1899,G,03:12:13.11,-25:18:17.7,For -IC1900,G,03:15:55.23,+37:09:14.8,Per -IC1901,G,03:16:02.61,+37:06:44.8,Per -IC1902,G,03:16:12.42,+37:10:38.8,Per -IC1903,GPair,03:13:10.35,-50:34:11.4,Hor -IC1903 NED01,G,03:13:08.56,-50:33:45.4,Hor -IC1903 NED02,G,03:13:12.55,-50:34:39.9,Hor -IC1904,G,03:15:00.77,-30:42:28.9,For -IC1905,Other,03:18:48.00,+41:21:55.6,Per -IC1906,G,03:16:05.65,-34:21:38.3,For -IC1907,Dup,03:19:54.15,+41:33:48.2,Per -IC1908,G,03:15:05.30,-54:49:11.9,Hor -IC1909,G,03:17:20.02,-33:41:24.2,For -IC1910,Other,03:17:57.79,-21:26:05.1,Eri -IC1911,*,03:20:49.18,+35:19:18.5,Per -IC1912,G,03:16:43.43,-50:39:17.6,Hor -IC1913,G,03:19:34.54,-32:27:54.1,For -IC1914,G,03:19:25.24,-49:35:59.0,Hor -IC1915,G,03:19:51.95,-50:41:28.3,Hor -IC1916,G,03:20:16.52,-49:02:30.9,Hor -IC1917,G,03:22:12.36,-53:11:06.7,Hor -IC1918,G,03:26:17.89,+04:32:27.7,Tau -IC1919,G,03:26:02.24,-32:53:40.4,For -IC1920,G,03:24:24.40,-52:42:48.9,Hor -IC1921,Other,03:24:42.11,-50:41:52.8,Hor -IC1922,G,03:24:43.03,-50:44:25.9,Hor -IC1923,Other,03:24:52.64,-50:33:21.0,Hor -IC1923 NED01,G,03:24:52.91,-50:33:20.0,Hor -IC1923 NED02,*,03:24:51.77,-50:33:20.5,Hor -IC1924,G,03:25:07.67,-51:42:12.4,Hor -IC1925,Other,03:25:15.69,-51:15:24.8,Hor -IC1926,G,03:25:19.03,-51:42:03.8,Hor -IC1927,**,03:25:19.69,-51:43:10.0,Hor -IC1928,G,03:27:29.18,-21:33:36.6,Eri -IC1929,G,03:25:25.85,-51:16:01.9,Hor -IC1930,G,03:28:46.26,+04:23:00.8,Tau -IC1931,G,03:28:57.72,+01:45:02.6,Tau -IC1932,G,03:25:54.03,-51:20:35.4,Hor -IC1933,G,03:25:39.89,-52:47:07.8,Hor -IC1934,G,03:31:14.02,+42:47:32.2,Per -IC1935,G,03:26:13.30,-50:00:37.6,Hor -IC1936,G,03:26:28.04,-51:19:23.5,Hor -IC1937,G,03:26:47.64,-48:42:09.2,Hor -IC1938,G,03:27:10.44,-53:00:35.6,Hor -IC1939,Other,03:27:44.57,-51:04:15.3,Hor -IC1940,G,03:27:42.34,-52:08:22.0,Hor -IC1941,Other,03:32:14.94,+24:23:01.4,Tau -IC1942,GPair,03:27:53.30,-52:40:37.0,Hor -IC1942 NED01,G,03:27:52.75,-52:40:38.9,Hor -IC1942 NED02,G,03:27:53.91,-52:40:32.9,Hor -IC1943,Dup,03:38:44.87,-44:06:02.2,Hor -IC1944,G,03:29:40.00,-47:59:46.2,Hor -IC1945,G,03:29:16.56,-52:37:37.8,Hor -IC1946,G,03:29:22.19,-52:37:09.6,Hor -IC1947,G,03:30:32.82,-50:20:18.7,Hor -IC1948,G,03:30:50.05,-47:57:53.5,Hor -IC1949,G,03:30:52.82,-47:58:45.3,Hor -IC1950,G,03:31:04.52,-50:26:00.3,Hor -IC1951,G,03:30:56.33,-53:07:34.3,Hor -IC1952,G,03:33:26.67,-23:42:46.0,Eri -IC1953,G,03:33:41.87,-21:28:43.1,Eri -IC1954,G,03:31:31.39,-51:54:17.4,Hor -IC1955,G,03:31:24.68,-57:14:30.7,Ret -IC1956,G,03:35:33.17,+05:04:01.2,Tau -IC1957,G,03:32:13.48,-52:27:23.9,Hor -IC1958,G,03:32:46.41,-51:26:30.1,Hor -IC1959,G,03:33:12.59,-50:24:51.3,Hor -IC1960,G,03:32:32.97,-57:12:23.8,Ret -IC1961,G,03:33:33.35,-48:57:02.3,Hor -IC1962,G,03:35:37.49,-21:17:38.6,Eri -IC1963,Dup,03:35:31.04,-34:26:49.4,For -IC1964,G,03:33:30.08,-53:10:23.3,Ret -IC1965,G,03:33:11.04,-56:33:14.7,Ret -IC1966,G,03:34:03.39,-51:19:20.1,Hor -IC1967,G,03:37:47.72,+03:16:15.7,Tau -IC1968,G,03:34:37.79,-50:39:04.6,Hor -IC1969,G,03:36:13.86,-45:10:46.6,Hor -IC1970,G,03:36:31.52,-43:57:24.6,Hor -IC1971,G,03:35:57.39,-52:39:04.5,Hor -IC1972,G,03:36:21.32,-51:58:05.3,Hor -IC1973,G,03:36:21.02,-51:59:39.0,Hor -IC1974,G,03:36:42.24,-49:33:01.0,Hor -IC1975,G,03:39:03.55,-15:30:00.5,Eri -IC1976,G,03:37:08.76,-47:26:16.5,Hor -IC1977,G,03:40:45.12,+17:44:28.2,Tau -IC1978,G,03:37:05.59,-50:09:03.0,Hor -IC1979,**,03:36:46.27,-57:56:40.4,Ret -IC1980,G,03:36:58.99,-57:58:25.9,Ret -IC1981,Dup,03:40:29.37,-26:51:44.1,For -IC1982,G,03:37:42.47,-57:46:34.6,Ret -IC1983,Dup,03:40:56.86,-22:33:52.1,Eri -IC1984,G,03:39:50.23,-47:04:33.0,Hor -IC1985,Dup,03:44:34.19,+32:09:46.2,Per -IC1986,G,03:40:35.15,-45:21:20.9,Hor -IC1987,GGroup,03:40:11.27,-55:03:17.3,Ret -IC1987 NED01,G,03:40:09.69,-55:03:17.6,Ret -IC1987 NED02,G,03:40:11.54,-55:03:32.7,Ret -IC1987 NED03,G,03:40:12.04,-55:03:12.4,Ret -IC1987 NED04,G,03:40:12.21,-55:03:22.6,Ret -IC1988,Other,03:42:45.61,-39:53:13.6,Eri -IC1989,G,03:41:54.67,-50:57:28.1,Hor -IC1990,Neb,03:47:13.83,+24:20:02.0,Tau -IC1991,G,03:44:46.79,-51:31:24.3,Hor -IC1992,Other,03:45:08.04,-51:00:16.9,Hor -IC1993,G,03:47:04.81,-33:42:35.5,For -IC1994,G,03:45:55.05,-51:38:36.0,Hor -IC1995,Neb,03:50:18.54,+25:34:50.8,Tau -IC1996,G,03:45:07.69,-57:19:29.3,Ret -IC1997,G,03:44:51.90,-59:08:15.6,Ret -IC1998,G,03:51:31.32,+01:11:23.3,Tau -IC1999,G,03:47:42.84,-56:57:07.5,Ret -IC2000,G,03:49:07.74,-48:51:29.5,Hor -IC2001,**,03:50:51.72,-48:35:58.7,Hor -IC2002,Dup,03:54:30.35,+10:42:25.2,Tau -IC2003,PN,03:56:22.03,+33:52:29.5,Per -IC2004,G,03:51:45.73,-49:25:10.6,Hor -IC2005,G,03:57:39.54,+36:47:14.9,Per -IC2006,G,03:54:28.45,-35:58:01.7,Eri -IC2007,G,03:55:22.76,-28:09:30.0,Eri -IC2008,Dup,03:55:22.76,-28:09:30.0,Eri -IC2009,G,03:53:34.85,-48:59:22.1,Hor -IC2010,G,03:51:58.03,-59:55:45.7,Ret -IC2011,**,03:52:27.15,-57:28:06.3,Ret -IC2012,GPair,03:52:55.32,-58:39:03.6,Ret -IC2012 NED01,G,03:52:55.26,-58:38:58.4,Ret -IC2013,Other,03:56:44.06,-17:06:34.2,Eri -IC2014,G,03:55:21.68,-56:44:46.7,Ret -IC2015,G,03:58:11.42,-40:23:20.9,Hor -IC2016,G,04:01:59.87,+20:14:25.6,Tau -IC2017,G,03:56:39.37,-59:23:41.1,Ret -IC2018,G,03:57:54.53,-52:46:52.0,Dor -IC2019,G,04:01:53.65,+05:38:22.0,Tau -IC2020,G,03:58:53.14,-54:03:27.4,Ret -IC2021,G,03:59:23.93,-52:39:24.2,Dor -IC2022,G,03:58:40.01,-59:02:37.2,Ret -IC2023,G,03:59:40.63,-52:40:52.1,Dor -IC2024,G,04:00:04.14,-53:22:15.6,Ret -IC2025,G,04:00:23.18,-53:03:56.2,Ret -IC2026,Dup,04:03:55.20,-11:10:44.5,Eri -IC2027,G,04:06:39.59,+37:06:56.7,Per -IC2028,G,04:01:18.23,-52:42:26.8,Dor -IC2029,G,04:01:17.95,-52:48:02.8,Dor -IC2030,Other,04:04:56.29,-19:13:53.3,Eri -IC2031,G,04:06:14.72,-05:39:06.8,Eri -IC2032,G,04:07:03.04,-55:19:25.8,Dor -IC2033,G,04:07:14.42,-53:40:51.3,Dor -IC2034,G,04:06:36.83,-57:57:41.0,Ret -IC2035,G,04:09:01.87,-45:31:03.1,Hor -IC2036,G,04:09:55.10,-39:41:19.3,Hor -IC2037,G,04:08:19.00,-58:45:04.2,Ret -IC2038,G,04:08:53.75,-55:59:22.4,Dor -IC2039,G,04:09:02.37,-56:00:42.1,Dor -IC2040,G,04:12:59.77,-32:33:11.8,Eri -IC2041,G,04:12:34.90,-32:49:02.5,Eri -IC2042,*,04:11:43.26,-47:16:12.3,Hor -IC2043,G,04:11:09.45,-53:41:11.9,Dor -IC2044,G,04:11:13.97,-54:31:57.2,Dor -IC2045,G,04:14:36.01,-13:10:29.7,Eri -IC2046,G,04:11:24.54,-54:40:23.5,Dor -IC2047,Dup,04:14:56.08,-13:11:30.2,Eri -IC2048,Dup,04:12:34.90,-32:49:02.5,Eri -IC2049,G,04:12:04.28,-58:33:25.2,Ret -IC2050,G,04:13:56.13,-53:28:31.3,Dor -IC2051,G,03:52:00.83,-83:49:50.5,Men -IC2052,G,04:14:58.52,-54:20:10.3,Dor -IC2053,G,04:15:55.62,-49:21:30.0,Dor -IC2054,G,04:07:26.34,-78:15:11.9,Men -IC2055,Other,04:17:48.52,-48:55:26.8,Dor -IC2056,G,04:16:24.54,-60:12:24.5,Ret -IC2057,G,04:21:56.09,+04:02:55.1,Tau -IC2058,G,04:17:54.35,-55:55:58.4,Dor -IC2059,G,04:20:26.30,-31:43:28.5,Eri -IC2060,G,04:17:53.36,-56:36:58.5,Ret -IC2061,Other,04:24:00.13,+21:04:59.5,Tau -IC2062,*,04:32:01.86,+71:55:11.5,Cam -IC2063,G,04:22:40.32,-15:39:37.8,Eri -IC2064,G,04:23:26.74,-15:41:06.6,Eri -IC2065,G,04:21:27.98,-55:55:59.9,Dor -IC2066,G,04:23:32.33,-54:44:00.2,Dor -IC2067,Neb,04:30:51.03,+35:26:46.2,Per -IC2068,G,04:26:36.88,-42:05:37.4,Cae -IC2069,Other,04:25:56.14,-48:12:29.0,Cae -IC2070,G,04:24:35.69,-57:58:51.2,Dor -IC2071,GGroup,04:26:13.19,-53:09:07.6,Dor -IC2072,Other,04:26:54.60,-48:22:39.0,Cae -IC2073,G,04:26:33.84,-53:11:14.4,Dor -IC2074,Other,04:31:23.03,+07:42:09.4,Tau -IC2075,Dup,04:30:51.59,-05:47:53.8,Eri -IC2076,Other,04:28:07.81,-48:13:43.9,Cae -IC2077,Dup,04:32:06.12,+00:34:02.5,Tau -IC2078,*,04:31:52.22,-04:41:55.3,Eri -IC2079,G,04:28:30.82,-53:44:16.5,Dor -IC2080,G,04:31:52.15,-05:45:24.6,Eri -IC2081,G,04:29:00.90,-53:36:49.3,Dor -IC2082,GPair,04:29:07.60,-53:49:38.0,Dor -IC2082 NED01,G,04:29:08.21,-53:49:40.6,Dor -IC2082 NED02,G,04:29:07.02,-53:49:36.1,Dor -IC2083,G,04:30:44.27,-53:58:51.0,Dor -IC2084,Other,04:32:06.17,-48:17:17.9,Cae -IC2085,G,04:31:24.24,-54:25:00.6,Dor -IC2086,G,04:31:32.17,-53:38:51.5,Dor -IC2087,Neb,04:39:59.97,+25:44:32.0,Tau -IC2088,Neb,04:31:04.56,+26:36:25.3,Tau -IC2089,G,04:32:50.44,-75:32:22.0,Men -IC2090,Other,04:44:44.07,-33:59:38.7,Cae -IC2091,Other,04:46:39.22,-04:40:18.5,Eri -IC2092,Other,04:46:47.62,-04:56:36.1,Eri -IC2093,*,04:47:32.28,-02:42:32.5,Ori -IC2094,G,04:48:24.83,-05:21:09.5,Eri -IC2095,G,04:48:45.63,-05:07:29.3,Eri -IC2096,Other,04:49:40.86,-04:58:43.1,Eri -IC2097,G,04:50:24.22,-05:04:52.4,Eri -IC2098,G,04:50:44.31,-05:25:07.2,Eri -IC2099,Dup,04:50:52.07,-04:53:33.8,Eri -IC2100,**,04:51:14.94,-04:49:51.3,Eri -IC2101,G,04:51:42.24,-06:13:53.2,Eri -IC2102,G,04:51:55.32,-04:57:05.8,Eri -IC2103,G,04:39:48.05,-76:50:12.5,Men -IC2104,G,04:56:18.96,-15:47:52.4,Lep -IC2105,Cl+N,04:49:26.66,-69:12:03.3,Dor -IC2106,G,04:56:33.89,-28:30:14.1,Cae -IC2107,Dup,04:58:20.61,+08:14:18.1,Ori -IC2108,Dup,04:57:17.07,-15:17:20.4,Lep -IC2109,*,04:58:59.18,-00:18:19.1,Ori -IC2110,*,04:59:01.80,-00:18:09.5,Ori -IC2111,Cl+N,04:51:52.12,-69:23:31.8,Dor -IC2112,G,05:00:30.15,+04:23:11.5,Ori -IC2113,Dup,04:59:31.83,-15:49:25.1,Lep -IC2114,Dup,04:54:25.97,-69:11:02.8,Dor -IC2115,*,04:57:08.72,-66:23:24.7,Dor -IC2116,HII,04:57:16.26,-66:23:20.5,Dor -IC2117,Cl+N,04:57:15.21,-68:26:29.3,Dor -IC2118,Dup,05:04:55.44,-07:15:56.3,Eri -IC2119,G,05:06:50.95,-20:20:42.7,Lep -IC2120,Other,05:19:10.30,+38:11:06.0,Aur -IC2121,Dup,05:19:44.86,-25:03:51.6,Lep -IC2122,G,05:19:01.40,-37:05:21.8,Col -IC2123,Dup,05:21:56.70,+03:29:11.0,Ori -IC2124,Dup,05:21:58.78,+03:28:55.8,Ori -IC2125,G,05:24:28.12,-27:00:57.8,Lep -IC2126,Dup,05:21:58.71,-67:57:26.6,Dor -IC2127,Dup,05:22:13.96,-67:58:41.9,Dor -IC2128,Cl+N,05:22:44.17,-68:03:39.9,Dor -IC2129,G,05:31:50.47,-23:08:42.2,Lep -IC2130,Dup,05:31:50.47,-23:08:42.2,Lep -IC2131,Dup,05:32:18.55,-17:13:25.9,Lep -IC2132,G,05:32:28.67,-13:55:37.6,Lep -IC2133,Dup,05:42:04.65,+69:22:42.4,Cam -IC2134,GCl,05:23:05.84,-75:26:48.6,Men -IC2135,G,05:33:12.90,-36:23:55.8,Col -IC2136,Dup,05:33:12.90,-36:23:55.8,Col -IC2137,G,05:34:21.68,-23:32:00.0,Lep -IC2138,Dup,05:34:21.68,-23:32:00.0,Lep -IC2139,*Ass,05:35:16.23,-17:56:01.2,Lep -IC2140,GCl,05:33:21.91,-75:22:31.3,Men -IC2141,*Ass,05:42:22.34,-51:01:58.3,Pic -IC2142,G,05:33:09.16,-78:01:09.9,Men -IC2143,G,05:46:52.61,-18:43:35.0,Lep -IC2144,HII,05:50:13.89,+23:52:20.7,Tau -IC2145,Neb,05:40:24.75,-69:40:13.1,Dor -IC2146,GCl,05:37:46.93,-74:46:59.5,Men -IC2147,G,05:43:28.06,-30:29:42.1,Col -IC2148,GCl,05:39:11.86,-75:33:44.8,Men -IC2149,PN,05:56:23.89,+46:06:17.2,Aur -IC2150,G,05:51:18.56,-38:19:13.7,Col -IC2151,G,05:52:36.43,-17:47:14.2,Lep -IC2152,G,05:57:53.41,-23:10:50.8,Lep -IC2153,GPair,06:00:05.18,-33:55:11.1,Col -IC2153 NED01,G,06:00:04.23,-33:55:12.3,Col -IC2153 NED02,G,06:00:05.44,-33:55:05.5,Col -IC2154,Dup,06:01:07.81,-23:40:21.5,Lep -IC2155,G,06:00:38.43,-33:59:50.4,Col -IC2156,OCl,06:04:52.41,+24:09:30.4,Gem -IC2157,OCl,06:04:47.58,+24:04:15.7,Gem -IC2158,G,06:05:17.96,-27:51:25.1,Col -IC2159,Neb,06:09:57.62,+20:25:53.0,Ori -IC2160,G,05:55:28.56,-76:55:12.9,Men -IC2161,GCl,05:57:24.86,-75:08:22.1,Men -IC2162,HII,06:13:04.70,+17:58:48.3,Ori -IC2163,G,06:16:27.98,-21:22:33.1,CMa -IC2164,G,06:06:52.26,-75:21:52.9,Men -IC2165,PN,06:21:42.70,-12:59:14.0,CMa -IC2166,G,06:26:55.64,+59:04:48.3,Lyn -IC2167,Dup,06:31:06.18,+10:27:33.4,Mon -IC2168,**,06:33:47.72,+44:41:07.7,Aur -IC2169,Dup,06:31:00.32,+09:53:50.8,Mon -IC2170,Other,06:34:04.91,+44:41:18.4,Aur -IC2171,G,06:44:27.69,-17:55:56.9,CMa -IC2172,Dup,06:46:51.57,+01:18:57.6,Mon -IC2173,*,06:50:47.16,+33:27:29.0,Gem -IC2174,G,07:09:05.61,+75:21:10.9,Cam -IC2175,**,07:08:39.66,+35:17:17.7,Gem -IC2176,G,07:07:31.83,+32:28:11.1,Gem -IC2177,RfN,07:04:36.91,-10:28:15.8,Mon -IC2178,G,07:07:37.66,+32:30:44.6,Gem -IC2179,G,07:15:32.31,+64:55:34.3,Cam -IC2180,G,07:11:19.58,+26:22:17.5,Gem -IC2181,G,07:13:10.35,+18:59:45.0,Gem -IC2182,G,07:14:11.03,+18:56:42.1,Gem -IC2183,Other,07:16:56.28,-20:24:37.7,CMa -IC2184,GGroup,07:29:25.40,+72:07:44.0,Cam -IC2184 NED01,G,07:29:24.16,+72:07:40.4,Cam -IC2184 NED02,G,07:29:27.02,+72:07:51.5,Cam -IC2185,G,07:23:16.04,+32:29:43.2,Gem -IC2186,G,07:22:47.78,+21:31:45.3,Gem -IC2187,G,07:22:43.31,+21:29:00.1,Gem -IC2188,G,07:22:43.18,+21:30:46.9,Gem -IC2189,Other,07:24:57.59,+08:55:14.5,CMi -IC2190,G,07:29:54.30,+37:27:06.3,Aur -IC2191,G,07:30:17.46,+24:19:39.8,Gem -IC2192,G,07:33:20.31,+31:21:41.0,Gem -IC2193,G,07:33:23.71,+31:29:00.8,Gem -IC2194,G,07:33:40.19,+31:20:03.9,Gem -IC2195,Other,07:28:27.63,-51:15:26.7,Car -IC2196,G,07:34:09.74,+31:24:20.4,Gem -IC2197,G,07:34:25.31,+31:25:19.2,Gem -IC2198,G,07:34:11.13,+23:57:58.8,Gem -IC2199,G,07:34:55.74,+31:16:34.5,Gem -IC2200,G,07:28:17.51,-62:21:10.5,Car -IC2201,G,07:36:16.81,+33:07:21.8,Gem -IC2202,G,07:27:54.74,-67:34:27.2,Vol -IC2203,G,07:40:33.61,+34:13:48.2,Gem -IC2204,G,07:41:18.10,+34:13:55.8,Gem -IC2205,G,07:46:54.57,+26:52:20.4,Gem -IC2206,*,07:45:50.39,-34:19:48.6,Pup -IC2207,G,07:49:50.89,+33:57:44.2,Gem -IC2208,G,07:52:07.84,+27:29:02.1,Gem -IC2209,G,07:56:14.21,+60:18:14.7,Cam -IC2210,**,07:56:56.49,+56:40:56.4,Lyn -IC2211,G,07:57:45.66,+32:33:29.2,Gem -IC2212,G,07:58:57.20,+32:36:44.1,Gem -IC2213,G,07:59:06.55,+27:27:50.4,Gem -IC2214,G,07:59:53.81,+33:17:25.9,Lyn -IC2215,Other,07:59:33.15,+24:55:44.3,Gem -IC2216,**,07:59:27.55,+05:36:52.2,CMi -IC2217,G,08:00:49.73,+27:30:01.1,Cnc -IC2218,G,08:01:38.49,+24:25:56.8,Cnc -IC2219,G,08:02:36.53,+27:26:15.0,Cnc -IC2220,RfN,07:56:50.95,-59:07:32.8,Car -IC2221,G,08:05:07.95,+37:27:02.3,Lyn -IC2222,G,08:05:14.77,+37:28:21.3,Lyn -IC2223,G,08:05:50.31,+37:27:36.2,Lyn -IC2224,Dup,08:05:50.31,+37:27:36.2,Lyn -IC2225,G,08:05:28.12,+35:56:48.1,Lyn -IC2226,G,08:06:11.22,+12:32:36.6,Cnc -IC2227,G,08:07:07.18,+36:14:00.5,Lyn -IC2228,*,08:07:05.65,+08:01:31.3,Cnc -IC2229,Dup,08:09:44.16,+25:52:53.8,Cnc -IC2230,G,08:10:56.55,+25:41:04.9,Cnc -IC2231,G,08:11:01.60,+05:05:14.4,CMi -IC2232,Dup,08:12:57.92,+36:15:16.7,Lyn -IC2233,G,08:13:58.91,+45:44:31.7,Lyn -IC2234,G,08:13:51.62,+35:29:34.5,Lyn -IC2235,**,08:13:33.83,+24:04:36.4,Cnc -IC2236,**,08:13:37.52,+24:02:55.7,Cnc -IC2237,*,08:14:08.09,+24:40:45.5,Cnc -IC2238,*,08:14:08.61,+24:39:42.8,Cnc -IC2239,G,08:14:06.79,+23:51:58.9,Cnc -IC2240,*,08:14:47.48,+24:28:03.2,Cnc -IC2241,**,08:15:08.64,+24:07:45.8,Cnc -IC2242,*,08:15:11.46,+24:07:58.6,Cnc -IC2243,*,08:15:18.43,+23:57:44.2,Cnc -IC2244,*,08:15:22.27,+24:32:45.0,Cnc -IC2245,*,08:15:28.42,+24:32:09.8,Cnc -IC2246,*,08:16:00.79,+23:50:59.1,Cnc -IC2247,G,08:15:59.10,+23:11:58.6,Cnc -IC2248,G,08:16:04.81,+23:08:02.5,Cnc -IC2249,G,08:16:34.43,+24:29:37.4,Cnc -IC2250,G,08:16:32.16,+23:37:58.7,Cnc -IC2251,**,08:16:38.79,+23:56:58.7,Cnc -IC2252,*,08:16:41.95,+24:41:38.0,Cnc -IC2253,G,08:16:33.88,+21:24:35.6,Cnc -IC2254,G,08:16:45.51,+24:46:48.8,Cnc -IC2255,**,08:16:43.18,+23:27:25.6,Cnc -IC2256,G,08:16:54.43,+24:10:36.6,Cnc -IC2257,**,08:17:10.81,+23:38:59.7,Cnc -IC2258,*,08:17:16.44,+23:34:39.1,Cnc -IC2259,*,08:17:18.15,+23:33:56.3,Cnc -IC2260,*,08:17:27.58,+24:40:23.7,Cnc -IC2261,**,08:17:32.82,+23:30:44.4,Cnc -IC2262,*,08:17:22.56,+18:27:16.2,Cnc -IC2263,*,08:17:40.92,+23:34:48.4,Cnc -IC2264,*,08:17:44.93,+23:42:53.1,Cnc -IC2265,*,08:17:50.23,+24:11:36.7,Cnc -IC2266,*,08:17:38.45,+18:24:37.3,Cnc -IC2267,G,08:18:01.61,+24:44:07.2,Cnc -IC2268,G,08:18:06.56,+24:47:47.2,Cnc -IC2269,G,08:18:08.76,+23:02:51.3,Cnc -IC2270,**,08:18:00.37,+19:05:51.6,Cnc -IC2271,G,08:18:19.70,+24:31:36.9,Cnc -IC2272,**,08:18:07.17,+18:44:09.2,Cnc -IC2273,*,08:18:12.82,+18:24:06.0,Cnc -IC2274,Other,08:18:14.01,+18:39:57.0,Cnc -IC2275,*,08:18:13.72,+18:24:41.2,Cnc -IC2276,Other,08:18:29.37,+18:28:39.8,Cnc -IC2277,*,08:18:32.44,+18:39:00.2,Cnc -IC2278,Other,08:18:34.45,+18:27:41.5,Cnc -IC2279,*,08:18:35.52,+18:34:04.6,Cnc -IC2280,*,08:18:38.52,+18:26:59.1,Cnc -IC2281,*,08:18:54.15,+18:54:32.8,Cnc -IC2282,G,08:19:15.53,+24:47:33.5,Cnc -IC2283,*,08:19:17.53,+24:47:11.1,Cnc -IC2284,*,08:18:58.68,+18:36:20.1,Cnc -IC2285,**,08:19:02.81,+18:54:52.8,Cnc -IC2286,*,08:19:04.09,+18:57:22.0,Cnc -IC2287,*,08:19:07.63,+19:24:01.7,Cnc -IC2288,G,08:19:22.38,+23:44:50.3,Cnc -IC2289,*,08:19:07.64,+18:29:53.8,Cnc -IC2290,G,08:19:15.86,+19:18:47.7,Cnc -IC2291,*,08:19:18.15,+18:30:30.2,Cnc -IC2292,*,08:19:21.99,+19:33:48.9,Cnc -IC2293,G,08:19:32.11,+21:23:39.5,Cnc -IC2294,*,08:19:26.04,+18:59:04.9,Cnc -IC2295,**,08:19:26.99,+18:24:51.2,Cnc -IC2296,*,08:19:28.55,+18:53:55.5,Cnc -IC2297,*,08:20:04.62,+18:22:55.1,Cnc -IC2298,*,08:20:07.08,+18:24:11.2,Cnc -IC2299,**,08:20:09.41,+19:20:14.8,Cnc -IC2300,*,08:20:12.60,+18:25:12.2,Cnc -IC2301,*,08:20:13.93,+18:26:01.5,Cnc -IC2302,*,08:20:17.28,+19:21:26.0,Cnc -IC2303,*,08:20:19.27,+19:25:08.4,Cnc -IC2304,**,08:20:35.69,+19:26:22.3,Cnc -IC2305,*,08:20:40.08,+19:27:10.5,Cnc -IC2306,*,08:20:39.40,+19:06:37.0,Cnc -IC2307,G,08:20:42.82,+19:26:26.4,Cnc -IC2308,GTrpl,08:20:45.25,+19:21:44.2,Cnc -IC2308 NED01,G,08:20:44.85,+19:21:44.0,Cnc -IC2309,G,08:20:43.61,+18:23:52.2,Cnc -IC2310,*,08:20:46.32,+18:27:48.6,Cnc -IC2311,G,08:18:45.98,-25:22:11.1,Pup -IC2312,GPair,08:20:53.38,+18:30:39.2,Cnc -IC2312 NED01,G,08:20:53.26,+18:30:30.7,Cnc -IC2312 NED02,G,08:20:53.55,+18:30:44.6,Cnc -IC2313,*,08:20:54.58,+18:30:51.1,Cnc -IC2314,**,08:21:03.64,+18:45:45.6,Cnc -IC2315,*,08:21:10.69,+18:54:54.9,Cnc -IC2316,**,08:21:15.20,+19:45:33.9,Cnc -IC2317,*,08:21:21.46,+18:50:39.6,Cnc -IC2318,*,08:21:32.76,+18:37:22.4,Cnc -IC2319,*,08:21:33.06,+18:28:36.2,Cnc -IC2320,*,08:21:35.40,+18:40:12.9,Cnc -IC2321,*,08:21:39.17,+18:28:08.9,Cnc -IC2322,*,08:21:38.98,+18:29:02.8,Cnc -IC2323,*,08:21:41.22,+18:36:47.7,Cnc -IC2324,*,08:21:58.73,+19:11:38.5,Cnc -IC2325,Other,08:22:08.73,+18:54:43.7,Cnc -IC2326,Other,08:22:12.14,+19:00:43.5,Cnc -IC2327,G,08:21:27.97,+03:10:09.4,Hya -IC2328,Other,08:22:17.38,+19:36:59.2,Cnc -IC2329,G,08:22:19.48,+19:24:57.5,Cnc -IC2330,*,08:22:23.14,+18:51:13.0,Cnc -IC2331,**,08:22:35.15,+19:40:46.8,Cnc -IC2332,*,08:22:39.63,+19:55:23.0,Cnc -IC2333,*,08:23:00.85,+19:04:54.7,Cnc -IC2334,*,08:22:59.97,+18:36:49.6,Cnc -IC2335,G,08:23:07.04,+19:24:28.9,Cnc -IC2336,*,08:23:19.04,+18:32:14.8,Cnc -IC2337,G,08:23:20.29,+18:32:06.8,Cnc -IC2338,G,08:23:32.67,+21:20:17.1,Cnc -IC2339,G,08:23:34.21,+21:20:51.5,Cnc -IC2340,G,08:23:30.05,+18:44:58.0,Cnc -IC2341,G,08:23:41.44,+21:26:05.6,Cnc -IC2342,*,08:23:32.10,+18:34:46.6,Cnc -IC2343,*,08:23:53.93,+19:01:32.3,Cnc -IC2344,*,08:23:54.74,+18:39:35.0,Cnc -IC2345,*,08:24:08.44,+19:57:07.8,Cnc -IC2346,*,08:24:10.90,+19:42:22.4,Cnc -IC2347,*,08:24:14.08,+18:46:25.9,Cnc -IC2348,G,08:24:20.25,+20:31:59.7,Cnc -IC2349,*,08:24:16.71,+19:00:29.0,Cnc -IC2350,Other,08:24:28.38,+19:33:07.4,Cnc -IC2351,*,08:24:30.20,+18:35:19.6,Cnc -IC2352,*,08:24:39.98,+19:36:10.0,Cnc -IC2353,*,08:24:37.70,+18:39:23.6,Cnc -IC2354,**,08:24:40.76,+18:39:59.2,Cnc -IC2355,**,08:24:51.81,+20:27:48.5,Cnc -IC2356,*,08:25:00.86,+19:29:51.2,Cnc -IC2357,*,08:25:04.56,+19:30:31.9,Cnc -IC2358,*,08:25:05.10,+19:29:42.7,Cnc -IC2359,Dup,08:25:12.07,+20:20:05.1,Cnc -IC2360,*,08:25:15.09,+19:30:59.2,Cnc -IC2361,G,08:25:44.50,+27:52:28.5,Cnc -IC2362,**,08:25:41.42,+19:56:32.0,Cnc -IC2363,G,08:25:45.42,+19:26:57.4,Cnc -IC2364,**,08:25:51.49,+19:45:35.0,Cnc -IC2365,G,08:26:18.06,+27:50:24.5,Cnc -IC2366,Dup,08:26:18.06,+27:50:24.5,Cnc -IC2367,G,08:24:10.08,-18:46:32.0,Pup -IC2368,*,08:26:01.29,+19:52:58.8,Cnc -IC2369,*,08:26:16.06,+20:13:56.1,Cnc -IC2370,*,08:26:22.82,+19:38:17.0,Cnc -IC2371,*,08:26:36.98,+19:47:54.9,Cnc -IC2372,*,08:26:40.62,+19:52:59.1,Cnc -IC2373,G,08:26:48.98,+20:21:53.4,Cnc -IC2374,G,08:28:22.14,+30:26:35.5,Cnc -IC2375,G,08:26:19.67,-13:18:11.3,Pup -IC2376,G,08:28:26.13,+30:24:27.7,Cnc -IC2377,G,08:26:26.08,-13:18:22.2,Pup -IC2378,G,08:28:31.65,+30:25:52.8,Cnc -IC2379,G,08:26:27.80,-13:17:34.3,Pup -IC2380,G,08:28:43.89,+30:24:16.3,Cnc -IC2381,**,08:28:21.74,+19:47:29.9,Cnc -IC2382,G,08:28:46.12,+22:03:12.5,Cnc -IC2383,G,08:29:41.35,+30:41:16.7,Cnc -IC2384,G,08:34:23.48,+32:26:05.2,Cnc -IC2385,G,08:35:10.36,+37:15:57.5,Lyn -IC2386,*,08:34:43.84,+25:48:24.1,Cnc -IC2387,G,08:38:34.00,+30:47:55.3,Cnc -IC2388,G,08:39:56.52,+19:38:43.1,Cnc -IC2389,G,08:47:57.77,+73:32:21.1,Cam -IC2390,Dup,08:41:51.74,+19:42:09.1,Cnc -IC2391,OCl,08:40:31.88,-53:02:07.7,Vel -IC2392,G,08:44:30.80,+18:17:10.1,Cnc -IC2393,G,08:46:49.19,+28:10:16.7,Cnc -IC2394,G,08:47:06.91,+28:14:11.6,Cnc -IC2395,OCl,08:42:30.11,-48:09:02.0,Vel -IC2396,*,08:46:40.59,+17:38:57.1,Cnc -IC2397,**,08:46:41.86,+17:39:35.2,Cnc -IC2398,G,08:46:44.57,+17:45:17.7,Cnc -IC2399,G,08:47:49.73,+18:54:42.8,Cnc -IC2400,G,08:47:59.15,+38:04:11.3,Lyn -IC2401,G,08:48:10.30,+37:45:19.3,Lyn -IC2402,G,08:47:59.04,+31:47:08.3,Cnc -IC2403,G,08:46:09.33,-15:21:25.2,Hya -IC2404,G,08:48:10.45,+29:29:28.9,Cnc -IC2405,G,08:48:42.76,+37:13:07.1,Lyn -IC2406,G,08:48:04.61,+17:42:08.6,Cnc -IC2407,G,08:48:09.15,+17:36:41.3,Cnc -IC2408,*,08:48:20.24,+19:02:14.7,Cnc -IC2409,G,08:48:24.66,+18:19:52.1,Cnc -IC2410,Dup,08:48:27.25,+19:01:10.2,Cnc -IC2411,Dup,08:48:30.17,+19:02:38.0,Cnc -IC2412,*,08:49:23.71,+18:32:35.4,Cnc -IC2413,**,08:49:31.57,+18:44:41.1,Cnc -IC2414,G,08:49:50.12,+18:47:32.7,Cnc -IC2415,*,08:50:01.91,+18:39:05.8,Cnc -IC2416,*,08:50:32.15,+18:33:34.6,Cnc -IC2417,*,08:51:08.16,+18:37:29.7,Cnc -IC2418,G,08:51:25.08,+17:56:42.9,Cnc -IC2419,**,08:52:09.40,+18:06:04.9,Cnc -IC2420,G,08:51:33.76,+03:06:02.5,Hya -IC2421,G,08:54:21.60,+32:40:51.1,Cnc -IC2422,G,08:54:24.31,+20:13:29.3,Cnc -IC2423,G,08:54:47.08,+20:13:13.0,Cnc -IC2424,Dup,08:56:47.69,+39:22:55.9,Lyn -IC2425,*,08:55:50.13,-03:25:23.3,Hya -IC2426,G,08:58:30.48,+02:55:31.8,Hya -IC2427,G,09:01:01.70,+37:52:31.6,Lyn -IC2428,G,09:03:14.70,+30:35:28.8,Cnc -IC2429,G,09:03:42.53,+29:17:45.6,Cnc -IC2430,G,09:04:22.83,+27:57:10.8,Cnc -IC2431,GGroup,09:04:35.35,+14:35:38.7,Cnc -IC2431 NED01,G,09:04:34.56,+14:35:52.4,Cnc -IC2431 NED02,G,09:04:34.72,+14:35:44.8,Cnc -IC2431 NED03,G,09:04:34.82,+14:35:36.3,Cnc -IC2431 NED04,G,09:04:35.44,+14:35:42.2,Cnc -IC2432,G,09:04:39.54,+05:30:43.4,Hya -IC2433,G,09:05:28.73,+22:36:08.0,Cnc -IC2434,G,09:07:16.06,+37:12:55.0,Lyn -IC2435,G,09:06:49.79,+26:16:31.6,Cnc -IC2436,**,09:05:23.72,-19:09:55.7,Hya -IC2437,G,09:05:33.11,-19:12:25.6,Hya -IC2438,Other,09:14:08.83,+73:25:02.5,Cam -IC2439,G,09:08:38.46,+32:35:34.6,Cnc -IC2440,*,09:15:50.16,+73:27:32.6,Cam -IC2441,GPair,09:10:02.30,+22:51:12.0,Cnc -IC2441 NED01,G,09:10:01.82,+22:51:18.2,Cnc -IC2441 NED02,G,09:10:02.90,+22:51:06.4,Cnc -IC2442,G,09:10:05.17,+22:50:18.0,Cnc -IC2443,G,09:11:30.89,+28:49:35.7,Cnc -IC2444,G,09:12:50.86,+30:12:44.2,Cnc -IC2445,G,09:13:12.60,+31:48:28.3,Cnc -IC2446,G,09:13:31.38,+28:57:06.3,Cnc -IC2447,Dup,09:13:31.38,+28:57:06.3,Cnc -IC2448,PN,09:07:06.26,-69:56:30.6,Car -IC2449,Dup,09:13:33.15,+30:00:00.5,Cnc -IC2450,G,09:17:05.28,+25:25:45.0,Cnc -IC2451,G,09:15:47.78,+23:29:47.1,Cnc -IC2452,G,09:15:57.58,+23:28:20.1,Cnc -IC2453,G,09:15:54.53,+20:55:44.0,Cnc -IC2454,G,09:16:01.77,+17:49:14.5,Cnc -IC2455,Dup,09:16:50.01,+20:11:54.6,Cnc -IC2456,G,09:17:24.24,+34:40:27.8,Lyn -IC2457,G,09:17:04.14,+20:05:36.6,Cnc -IC2458,Dup,09:21:30.07,+64:14:19.3,UMa -IC2459,G,09:18:59.47,+34:51:43.9,Lyn -IC2460,Dup,09:19:19.01,+33:52:50.9,Lyn -IC2461,G,09:19:58.03,+37:11:28.5,Lyn -IC2462,G,09:22:56.25,+22:41:10.9,Leo -IC2463,G,09:23:00.25,+22:37:06.8,Leo -IC2464,G,09:23:22.27,+22:37:48.9,Leo -IC2465,G,09:23:31.51,+24:26:44.5,Leo -IC2466,G,09:23:45.03,+24:31:06.4,Leo -IC2467,G,09:24:52.72,+38:21:06.3,LMi -IC2468,G,09:25:01.49,+38:20:38.5,LMi -IC2469,G,09:23:01.06,-32:26:59.1,Pyx -IC2470,G,09:25:41.32,+23:21:41.8,Leo -IC2471,G,09:25:12.17,-06:49:47.7,Hya -IC2472,G,09:26:33.72,+21:23:06.1,Leo -IC2473,G,09:27:23.50,+30:26:26.9,Leo -IC2474,G,09:27:11.41,+23:02:03.4,Leo -IC2475,G,09:27:54.33,+29:47:30.7,Leo -IC2476,G,09:27:52.83,+29:59:08.7,Leo -IC2477,G,09:28:17.83,+29:42:21.8,Leo -IC2478,G,09:28:00.93,+30:02:13.1,Leo -IC2479,G,09:28:04.09,+29:59:29.2,Leo -IC2480,Dup,09:28:17.83,+29:42:21.8,Leo -IC2481,G,09:27:28.81,+03:55:46.6,Hya -IC2482,G,09:26:59.22,-12:06:32.1,Hya -IC2483,G,09:29:25.79,+30:59:41.0,Leo -IC2484,Other,09:26:50.28,-42:50:34.1,Vel -IC2485,Other,09:27:11.88,-39:17:05.0,Ant -IC2486,G,09:30:17.37,+26:38:28.6,Leo -IC2487,G,09:30:09.17,+20:05:27.1,Leo -IC2488,OCl,09:27:38.23,-57:00:25.0,Vel -IC2489,Other,09:31:11.63,-05:53:03.0,Hya -IC2490,G,09:33:03.65,+29:55:42.1,Leo -IC2491,G,09:35:14.22,+34:43:54.0,LMi -IC2492,G,09:33:14.87,-37:51:58.8,Ant -IC2493,G,09:36:17.54,+37:21:50.4,LMi -IC2494,Dup,09:36:05.79,-12:26:12.2,Hya -IC2495,G,09:38:07.39,+28:03:27.6,Leo -IC2496,G,09:38:44.50,+34:43:36.4,LMi -IC2497,G,09:41:04.09,+34:43:57.8,LMi -IC2498,G,09:41:21.94,+28:06:52.1,Leo -IC2499,G,09:41:24.63,+27:53:42.9,Leo -IC2500,G,09:42:23.37,+36:20:58.9,LMi -IC2501,PN,09:38:47.18,-60:05:30.7,Car -IC2502,G,09:43:15.40,+35:09:37.9,LMi -IC2503,G,09:43:15.84,+35:12:22.4,LMi -IC2504,Other,09:38:33.82,-69:05:06.4,Car -IC2505,G,09:45:06.95,+27:16:06.9,Leo -IC2506,G,09:45:12.77,+27:15:07.2,Leo -IC2507,G,09:44:33.90,-31:47:24.0,Ant -IC2508,G,09:47:07.08,+33:30:29.0,LMi -IC2509,*,09:46:55.92,+05:42:07.0,Sex -IC2510,G,09:47:43.48,-32:50:14.8,Ant -IC2511,G,09:49:24.55,-32:50:21.1,Ant -IC2512,Dup,09:49:24.55,-32:50:21.1,Ant -IC2513,G,09:50:00.79,-32:52:58.2,Ant -IC2514,Dup,09:50:00.79,-32:52:58.2,Ant -IC2515,G,09:54:39.42,+37:24:30.9,LMi -IC2516,G,09:54:48.36,+37:41:13.0,LMi -IC2517,G,09:52:50.67,-33:44:31.0,Ant -IC2518,G,09:55:58.53,+37:09:19.9,LMi -IC2519,G,09:55:58.81,+34:02:11.7,LMi -IC2520,G,09:56:20.12,+27:13:39.3,Leo -IC2521,G,09:57:15.75,+33:58:34.7,LMi -IC2522,G,09:55:08.96,-33:08:13.7,Ant -IC2523,G,09:55:09.52,-33:12:36.8,Ant -IC2524,G,09:57:32.86,+33:37:11.0,LMi -IC2525,G,09:58:24.96,+37:06:06.7,LMi -IC2526,G,09:57:03.03,-32:15:24.7,Ant -IC2527,G,10:00:06.49,+38:10:19.9,LMi -IC2528,Dup,09:59:06.42,-27:07:43.7,Ant -IC2529,Dup,09:59:29.54,-22:49:34.6,Hya -IC2530,G,10:01:31.06,+37:12:14.2,LMi -IC2531,G,09:59:55.77,-29:37:01.1,Ant -IC2532,G,10:00:05.40,-34:13:41.8,Ant -IC2533,G,10:00:31.67,-31:14:42.0,Ant -IC2534,G,10:01:29.86,-34:06:44.7,Ant -IC2535,G,10:04:31.83,+38:00:21.7,LMi -IC2536,G,10:03:30.22,-33:56:59.1,Ant -IC2537,G,10:03:51.89,-27:34:15.1,Ant -IC2538,G,10:03:56.49,-34:48:27.1,Ant -IC2539,G,10:04:16.21,-31:21:46.6,Ant -IC2540,G,10:06:46.71,+31:28:32.6,LMi -IC2541,G,10:05:48.01,-17:26:04.4,Hya -IC2542,G,10:07:50.54,+34:18:55.1,LMi -IC2543,GPair,10:08:23.80,+37:50:34.0,LMi -IC2543 NED01,G,10:08:23.56,+37:50:29.5,LMi -IC2543 NED02,G,10:08:23.97,+37:50:39.2,LMi -IC2544,G,10:08:29.73,+33:20:47.0,LMi -IC2545,**,10:06:35.06,-33:51:29.8,Ant -IC2546,G,10:07:05.92,-33:15:40.9,Ant -IC2547,G,10:10:04.49,+36:30:08.8,LMi -IC2548,G,10:07:54.95,-35:13:46.6,Ant -IC2549,G,10:10:10.16,+36:27:53.5,LMi -IC2550,G,10:10:27.92,+27:57:22.0,LMi -IC2551,G,10:10:40.32,+24:24:50.9,Leo -IC2552,G,10:10:46.14,-34:50:40.9,Ant -IC2553,PN,10:09:20.87,-62:36:49.3,Car -IC2554,G,10:08:50.56,-67:01:51.1,Car -IC2555,Dup,10:11:42.43,-31:38:34.3,Ant -IC2556,G,10:12:37.63,-34:43:43.9,Ant -IC2557,G,10:16:05.87,+38:06:33.2,LMi -IC2558,G,10:14:44.14,-34:20:19.4,Ant -IC2559,G,10:14:45.39,-34:03:31.7,Ant -IC2560,G,10:16:18.72,-33:33:49.7,Ant -IC2561,G,10:19:08.61,+34:40:30.1,LMi -IC2562,G,10:18:54.50,+16:09:19.9,Leo -IC2563,G,10:18:51.93,-32:35:47.6,Ant -IC2564,G,10:21:27.70,+36:27:07.4,LMi -IC2565,G,10:21:17.87,+27:55:48.3,LMi -IC2566,G,10:22:19.39,+36:34:58.9,LMi -IC2567,G,10:21:57.80,+24:39:18.8,Leo -IC2568,G,10:22:30.01,+36:35:57.5,LMi -IC2569,G,10:22:53.59,+24:36:23.1,Leo -IC2570,G,10:21:34.30,-33:37:23.6,Ant -IC2571,Dup,10:21:35.08,-34:16:00.5,Ant -IC2572,G,10:25:07.28,+28:05:41.3,LMi -IC2573,G,10:23:30.18,-35:27:20.1,Ant -IC2574,G,10:28:23.48,+68:24:43.7,UMa -IC2575,G,10:25:24.00,-32:38:10.5,Ant -IC2576,G,10:25:58.98,-32:54:12.5,Ant -IC2577,G,10:28:01.45,+32:45:50.0,LMi -IC2578,G,10:27:22.69,-33:52:38.4,Ant -IC2579,Dup,10:29:16.84,+26:05:57.3,Leo -IC2580,G,10:28:17.98,-31:31:04.9,Ant -IC2581,OCl,10:27:29.15,-57:37:02.3,Car -IC2582,G,10:29:11.00,-30:20:33.0,Ant -IC2583,G,10:31:10.44,+26:03:18.2,Leo -IC2584,G,10:29:51.49,-34:54:41.9,Ant -IC2585,Dup,10:30:26.49,-35:21:34.2,Ant -IC2586,G,10:31:02.40,-28:42:59.9,Hya -IC2587,G,10:30:59.60,-34:33:46.6,Ant -IC2588,G,10:31:50.14,-30:23:04.3,Ant -IC2589,G,10:32:20.82,-24:02:15.2,Hya -IC2590,G,10:36:16.58,+26:57:44.9,Leo -IC2591,G,10:36:38.67,+35:03:10.5,LMi -IC2592,Dup,10:35:08.28,-43:41:30.5,Vel -IC2593,G,10:36:15.95,-12:43:32.9,Hya -IC2594,G,10:36:04.17,-24:19:23.1,Hya -IC2595,Other,10:37:33.12,-11:07:00.2,Sex -IC2596,G,10:34:12.46,-73:14:24.5,Car -IC2597,G,10:37:47.45,-27:04:54.1,Hya -IC2598,G,10:39:42.33,+26:43:39.2,LMi -IC2599,HII,10:37:27.09,-58:44:00.2,Car -IC2600,G,10:46:38.80,+72:19:13.6,UMa -IC2601,G,10:47:13.32,+72:19:22.9,UMa -IC2602,OCl,10:42:57.47,-64:23:39.1,Car -IC2603,Other,10:48:25.36,+32:55:38.3,LMi -IC2604,G,10:49:25.07,+32:46:21.8,LMi -IC2605,Other,10:49:47.54,+32:58:23.5,LMi -IC2606,G,10:50:17.65,+37:57:22.4,LMi -IC2607,G,10:50:18.94,+37:59:38.2,LMi -IC2608,G,10:50:15.52,+32:46:05.4,LMi -IC2609,Dup,10:50:17.98,-12:06:31.4,Hya -IC2610,Other,10:52:08.07,+33:04:59.4,LMi -IC2611,*,10:52:38.93,+10:08:10.9,Leo -IC2612,G,10:53:37.16,+32:46:03.5,LMi -IC2613,Dup,10:49:50.11,+32:58:58.3,LMi -IC2614,G,11:01:33.80,+38:48:13.2,UMa -IC2615,G,11:02:02.28,+37:56:42.9,UMa -IC2616,G,11:02:05.76,+38:47:14.1,UMa -IC2617,G,11:02:07.63,+38:39:51.3,UMa -IC2618,**,11:01:58.82,+27:47:21.3,LMi -IC2619,G,11:02:15.26,+37:57:57.9,UMa -IC2620,G,11:02:23.95,+38:30:18.1,UMa -IC2621,PN,11:00:19.99,-65:14:57.7,Car -IC2622,Dup,11:02:59.67,-16:17:22.0,Crt -IC2623,G,11:03:50.96,-20:05:35.0,Crt -IC2624,Dup,11:07:18.07,-19:28:17.6,Crt -IC2625,Dup,11:07:19.12,-19:33:20.3,Crt -IC2626,G,11:09:03.87,+26:54:15.0,Leo -IC2627,G,11:09:53.39,-23:43:33.4,Crt -IC2628,G,11:11:37.87,+12:07:19.1,Leo -IC2629,G,11:12:36.94,+12:06:17.8,Leo -IC2630,*,11:12:43.19,+12:19:08.2,Leo -IC2631,Neb,11:09:52.79,-76:36:51.5,Cha -IC2632,G,11:13:05.94,+11:40:23.8,Leo -IC2633,G,11:13:10.04,+11:36:03.6,Leo -IC2634,G,11:13:28.25,+10:29:09.5,Leo -IC2635,**,11:13:29.81,+11:27:50.0,Leo -IC2636,G,11:13:34.04,+11:27:22.2,Leo -IC2637,G,11:13:49.75,+09:35:10.7,Leo -IC2638,G,11:13:51.91,+10:33:48.2,Leo -IC2639,G,11:13:55.54,+09:38:34.1,Leo -IC2640,G,11:14:05.49,+10:59:51.6,Leo -IC2641,*,11:14:10.55,+09:23:57.6,Leo -IC2642,Other,11:14:15.80,+12:15:56.4,Leo -IC2643,*,11:14:26.56,+10:07:34.4,Leo -IC2644,G,11:14:29.81,+10:46:06.5,Leo -IC2645,G,11:14:30.82,+11:53:12.4,Leo -IC2646,G,11:14:37.60,+12:31:42.6,Leo -IC2647,*,11:14:38.62,+12:08:31.2,Leo -IC2648,G,11:14:45.63,+10:13:29.4,Leo -IC2649,G,11:14:46.45,+11:07:39.7,Leo -IC2650,G,11:14:52.66,+13:51:08.9,Leo -IC2651,G,11:14:52.28,+12:14:23.2,Leo -IC2652,G,11:14:52.32,+12:26:53.0,Leo -IC2653,**,11:14:53.83,+10:32:54.1,Leo -IC2654,G,11:15:02.86,+12:29:58.1,Leo -IC2655,G,11:15:05.16,+12:09:51.8,Leo -IC2656,G,11:15:05.38,+12:22:44.9,Leo -IC2657,G,11:15:08.71,+13:41:41.0,Leo -IC2658,*,11:15:08.75,+12:59:47.8,Leo -IC2659,*,11:15:27.84,+12:53:15.6,Leo -IC2660,G,11:15:28.44,+12:26:13.9,Leo -IC2661,G,11:15:29.17,+13:36:31.2,Leo -IC2662,Other,11:15:30.79,+12:46:15.8,Leo -IC2663,*,11:15:32.38,+12:36:14.1,Leo -IC2664,**,11:15:38.44,+12:33:45.7,Leo -IC2665,G,11:15:40.78,+11:43:26.9,Leo -IC2666,G,11:15:43.77,+13:46:56.1,Leo -IC2667,G,11:15:44.04,+12:07:00.6,Leo -IC2668,G,11:15:32.28,-14:10:15.9,Crt -IC2669,**,11:15:53.22,+13:25:46.4,Leo -IC2670,G,11:15:59.56,+11:47:00.1,Leo -IC2671,*,11:16:03.32,+13:07:27.3,Leo -IC2672,*,11:16:03.82,+10:09:26.2,Leo -IC2673,G,11:16:04.13,+10:09:45.2,Leo -IC2674,G,11:16:08.25,+11:02:55.1,Leo -IC2675,*,11:16:10.81,+12:14:57.5,Leo -IC2676,G,11:16:18.51,+09:49:18.6,Leo -IC2677,G,11:16:19.30,+12:12:57.1,Leo -IC2678,G,11:16:21.64,+11:56:56.8,Leo -IC2679,G,11:16:23.20,+12:00:55.4,Leo -IC2680,G,11:16:25.53,+09:48:25.7,Leo -IC2681,Other,11:16:33.25,+11:12:25.9,Leo -IC2682,**,11:16:36.12,+09:24:38.8,Leo -IC2683,G,11:16:54.30,+12:05:57.4,Leo -IC2684,G,11:17:01.05,+13:05:58.7,Leo -IC2685,*,11:17:00.09,+10:05:38.7,Leo -IC2686,*,11:17:02.51,+12:57:06.0,Leo -IC2687,**,11:17:11.98,+10:09:29.2,Leo -IC2688,NonEx,,, -IC2689,G,11:17:19.50,+12:57:35.8,Leo -IC2690,G,11:17:21.59,+12:58:31.5,Leo -IC2691,*,11:17:24.58,+12:01:51.9,Leo -IC2692,G,11:17:33.22,+10:46:05.0,Leo -IC2693,*,11:17:36.31,+13:32:55.8,Leo -IC2694,G,11:17:38.60,+13:22:34.0,Leo -IC2695,G,11:17:48.57,+13:43:39.6,Leo -IC2696,*,11:17:48.93,+12:45:20.7,Leo -IC2697,*,11:17:51.13,+13:23:59.9,Leo -IC2698,G,11:17:51.02,+11:53:08.8,Leo -IC2699,*,11:17:52.65,+11:54:33.2,Leo -IC2700,G,11:17:54.18,+12:03:15.1,Leo -IC2701,G,11:17:57.02,+11:07:05.0,Leo -IC2702,G,11:17:57.23,+09:24:44.9,Leo -IC2703,G,11:18:05.13,+17:38:58.3,Leo -IC2704,G,11:18:04.01,+12:27:15.1,Leo -IC2705,*,11:18:03.68,+11:54:14.7,Leo -IC2706,*,11:18:29.20,+12:32:54.4,Leo -IC2707,G,11:18:30.84,+09:28:29.5,Leo -IC2708,G,11:18:34.61,+12:42:40.0,Leo -IC2709,G,11:18:42.16,+12:33:42.1,Leo -IC2710,**,11:18:44.32,+13:33:59.7,Leo -IC2711,*,11:18:46.49,+13:44:18.4,Leo -IC2712,G,11:18:52.78,+09:37:36.8,Leo -IC2713,G,11:19:10.22,+12:09:53.3,Leo -IC2714,OCl,11:17:27.35,-62:43:30.4,Car -IC2715,G,11:19:14.39,+11:57:07.8,Leo -IC2716,G,11:19:16.34,+11:41:54.8,Leo -IC2717,*,11:19:18.77,+12:02:54.8,Leo -IC2718,G,11:19:20.90,+12:01:20.2,Leo -IC2719,G,11:19:32.20,+12:03:35.2,Leo -IC2720,G,11:19:35.65,+12:04:35.7,Leo -IC2721,Other,11:19:42.81,+12:18:38.3,Leo -IC2722,GTrpl,11:19:44.36,+13:57:46.5,Leo -IC2722 NED01,G,11:19:43.94,+13:57:50.1,Leo -IC2722 NED03,G,11:19:44.36,+13:57:46.5,Leo -IC2723,G,11:19:47.90,+12:02:00.4,Leo -IC2724,G,11:19:48.44,+10:42:59.6,Leo -IC2725,G,11:19:57.44,+13:25:45.4,Leo -IC2726,Other,11:19:58.44,+13:24:56.1,Leo -IC2727,G,11:20:00.07,+12:02:00.0,Leo -IC2728,*,11:20:05.13,+13:25:37.2,Leo -IC2729,G,11:20:06.77,+13:24:33.5,Leo -IC2730,*,11:20:07.39,+12:21:59.5,Leo -IC2731,*,11:20:10.27,+13:33:29.8,Leo -IC2732,G,11:20:12.38,+12:24:14.5,Leo -IC2733,**,11:20:17.31,+13:50:06.9,Leo -IC2734,G,11:20:23.82,+12:26:34.9,Leo -IC2735,G,11:21:03.90,+34:20:38.2,UMa -IC2736,*,11:20:54.62,+12:24:31.2,Leo -IC2737,*,11:21:08.25,+14:17:35.5,Leo -IC2738,G,11:21:23.06,+34:21:24.0,UMa -IC2739,G,11:21:12.43,+11:54:53.1,Leo -IC2740,G,11:21:17.06,+08:45:08.3,Leo -IC2741,G,11:21:17.47,+09:09:08.5,Leo -IC2742,G,11:21:18.73,+10:26:48.2,Leo -IC2743,*,11:21:24.99,+08:41:35.2,Leo -IC2744,G,11:21:42.51,+34:21:46.1,UMa -IC2745,G,11:21:31.76,+13:25:35.8,Leo -IC2746,G,11:21:36.42,+11:44:13.5,Leo -IC2747,**,11:21:40.16,+08:48:11.7,Leo -IC2748,G,11:21:44.04,+08:48:17.9,Leo -IC2749,G,11:21:45.20,+08:34:29.2,Leo -IC2750,G,11:21:50.79,+09:39:30.4,Leo -IC2751,G,11:22:07.39,+34:21:58.9,UMa -IC2752,G,11:22:01.94,+14:07:28.2,Leo -IC2753,G,11:21:59.71,+09:52:41.0,Leo -IC2754,G,11:22:02.38,+14:08:38.6,Leo -IC2755,*,11:22:02.41,+13:47:35.2,Leo -IC2756,G,11:22:00.91,+09:57:36.6,Leo -IC2757,G,11:22:02.10,+08:23:37.8,Leo -IC2758,G,11:22:03.33,+07:48:48.7,Leo -IC2759,G,11:22:13.28,+24:19:01.8,Leo -IC2760,G,11:22:12.80,+12:39:55.5,Leo -IC2761,G,11:22:17.17,+14:10:39.3,Leo -IC2762,G,11:22:17.92,+12:43:21.1,Leo -IC2763,G,11:22:18.56,+13:03:54.2,Leo -IC2764,G,11:27:05.03,-28:58:48.8,Hya -IC2765,G,11:22:23.12,+14:11:56.6,Leo -IC2766,G,11:22:23.06,+12:54:12.5,Leo -IC2767,G,11:22:23.19,+13:04:40.1,Leo -IC2768,G,11:22:23.56,+12:31:44.1,Leo -IC2769,G,11:22:25.65,+14:11:45.2,Leo -IC2770,G,11:22:24.76,+09:13:14.4,Leo -IC2771,G,11:22:28.05,+12:31:09.0,Leo -IC2772,*,11:22:30.40,+13:35:56.9,Leo -IC2773,*,11:22:35.31,+13:34:28.3,Leo -IC2774,*,11:22:37.15,+12:30:53.8,Leo -IC2775,G,11:22:39.56,+12:30:43.2,Leo -IC2776,G,11:22:39.99,+13:19:49.9,Leo -IC2777,G,11:22:40.55,+12:01:32.1,Leo -IC2778,Other,11:22:41.93,+12:31:34.7,Leo -IC2779,G,11:22:44.51,+13:20:43.1,Leo -IC2780,G,11:22:48.13,+10:08:58.4,Leo -IC2781,G,11:22:50.67,+12:20:41.6,Leo -IC2782,G,11:22:55.36,+13:26:28.6,Leo -IC2783,G,11:22:53.63,+08:53:02.6,Leo -IC2784,G,11:23:11.63,+13:07:03.8,Leo -IC2785,G,11:23:15.36,+13:23:28.5,Leo -IC2786,G,11:23:17.50,+13:23:31.2,Leo -IC2787,G,11:23:19.08,+13:37:47.2,Leo -IC2788,G,11:23:26.97,+12:41:52.9,Leo -IC2789,G,11:23:32.66,+14:11:16.9,Leo -IC2790,G,11:23:34.06,+09:33:19.3,Leo -IC2791,G,11:23:37.62,+12:53:44.8,Leo -IC2792,G,11:23:41.51,+11:24:17.4,Leo -IC2793,GPair,11:23:47.39,+09:26:59.2,Leo -IC2793 NED01,G,11:23:47.28,+09:27:04.8,Leo -IC2793 NED02,G,11:23:47.54,+09:26:54.4,Leo -IC2794,*,11:24:03.65,+12:47:27.5,Leo -IC2795,G,11:24:04.07,+12:08:06.4,Leo -IC2796,GPair,11:24:08.35,+09:20:38.9,Leo -IC2796 NED01,G,11:24:08.39,+09:20:44.6,Leo -IC2796 NED02,G,11:24:08.42,+09:20:34.5,Leo -IC2797,G,11:24:21.06,+11:42:21.4,Leo -IC2798,G,11:24:23.99,+12:24:56.2,Leo -IC2799,G,11:24:26.67,+13:50:56.8,Leo -IC2800,G,11:24:27.08,+12:12:31.7,Leo -IC2801,G,11:24:29.03,+10:11:01.8,Leo -IC2802,G,11:24:30.36,+12:12:31.5,Leo -IC2803,G,11:24:35.41,+09:51:00.1,Leo -IC2804,G,11:24:55.73,+13:13:19.4,Leo -IC2805,*,11:24:59.89,+14:00:52.1,Leo -IC2806,*,11:25:15.30,+09:39:08.0,Leo -IC2807,G,11:25:17.05,+11:31:48.2,Leo -IC2808,*,11:25:26.88,+09:07:55.4,Leo -IC2809,*,11:25:37.77,+08:31:34.6,Leo -IC2810,G,11:25:45.05,+14:40:35.7,Leo -IC2811,G,11:25:44.65,+09:10:13.9,Leo -IC2812,G,11:25:55.82,+11:31:47.7,Leo -IC2813,G,11:26:06.50,+11:15:20.7,Leo -IC2814,G,11:26:08.51,+09:39:42.5,Leo -IC2815,G,11:26:16.55,+12:48:13.8,Leo -IC2816,G,11:26:18.28,+10:38:11.4,Leo -IC2817,*,11:26:18.81,+09:08:56.4,Leo -IC2818,G,11:26:26.87,+12:55:15.4,Leo -IC2819,G,11:26:27.44,+13:50:41.6,Leo -IC2820,G,11:26:26.85,+10:14:18.5,Leo -IC2821,G,11:26:34.88,+13:57:46.5,Leo -IC2822,G,11:26:34.05,+11:26:24.3,Leo -IC2823,G,11:26:44.67,+12:50:54.3,Leo -IC2824,*,11:27:04.87,+14:05:07.0,Leo -IC2825,*,11:27:03.56,+08:26:38.1,Leo -IC2826,G,11:27:06.08,+13:14:19.4,Leo -IC2827,*,11:27:09.67,+11:30:51.8,Leo -IC2828,G,11:27:10.94,+08:43:51.8,Leo -IC2829,G,11:27:14.98,+10:19:20.5,Leo -IC2830,G,11:27:21.58,+07:48:51.5,Leo -IC2831,*,11:27:22.61,+08:58:44.1,Leo -IC2832,*,11:27:25.12,+13:59:22.0,Leo -IC2833,*,11:27:26.11,+13:36:10.1,Leo -IC2834,G,11:27:31.82,+13:34:13.2,Leo -IC2835,G,11:27:31.59,+12:08:34.4,Leo -IC2836,*,11:27:37.21,+09:05:05.3,Leo -IC2837,G,11:27:41.96,+10:18:47.0,Leo -IC2838,G,11:27:45.24,+14:00:40.5,Leo -IC2839,G,11:27:45.42,+10:49:11.1,Leo -IC2840,G,11:27:47.60,+13:25:33.2,Leo -IC2841,*,11:27:48.86,+12:36:11.2,Leo -IC2842,G,11:27:47.69,+09:39:07.1,Leo -IC2843,G,11:27:58.09,+13:11:02.1,Leo -IC2844,G,11:27:58.10,+11:27:11.8,Leo -IC2845,G,11:28:00.46,+12:31:47.1,Leo -IC2846,G,11:28:00.49,+11:09:29.8,Leo -IC2847,G,11:28:03.39,+13:55:49.4,Leo -IC2848,G,11:28:13.67,+13:01:49.7,Leo -IC2849,*,11:28:11.66,+09:05:38.4,Leo -IC2850,G,11:28:12.96,+09:03:44.1,Leo -IC2851,G,11:28:14.61,+11:23:39.8,Leo -IC2852,G,11:28:14.05,+09:48:01.7,Leo -IC2853,G,11:28:14.86,+09:08:49.3,Leo -IC2854,*,11:28:19.87,+08:58:07.4,Leo -IC2855,G,11:28:24.99,+09:41:15.9,Leo -IC2856,G,11:28:16.31,-12:53:26.5,Crt -IC2857,G,11:28:31.04,+09:06:15.9,Leo -IC2858,G,11:28:35.96,+13:39:41.4,Leo -IC2859,*,11:28:41.76,+09:06:30.6,Leo -IC2860,G,11:28:44.60,+14:02:30.8,Leo -IC2861,G,11:28:58.97,+38:51:04.6,UMa -IC2862,G,11:28:43.32,+10:07:38.2,Leo -IC2863,*,11:28:53.98,+09:05:43.1,Leo -IC2864,G,11:28:59.67,+12:22:03.8,Leo -IC2865,*,11:28:59.75,+09:06:55.8,Leo -IC2866,*,11:29:00.03,+09:02:31.9,Leo -IC2867,G,11:29:00.54,+09:05:21.8,Leo -IC2868,*,11:29:05.80,+09:05:39.5,Leo -IC2869,*,11:29:08.72,+09:01:02.6,Leo -IC2870,G,11:29:12.42,+11:51:55.8,Leo -IC2871,G,11:29:20.69,+08:36:08.5,Leo -IC2872,Neb,11:28:08.03,-62:59:20.2,Cen -IC2873,GPair,11:29:27.43,+13:13:05.9,Leo -IC2873 NED01,G,11:29:27.47,+13:12:56.5,Leo -IC2873 NED02,G,11:29:27.64,+13:13:12.4,Leo -IC2874,G,11:29:27.55,+10:37:45.0,Leo -IC2875,*,11:29:34.93,+12:59:23.6,Leo -IC2876,G,11:29:33.60,+09:00:58.2,Leo -IC2877,G,11:29:37.74,+12:51:11.8,Leo -IC2878,G,11:29:38.21,+09:58:03.3,Leo -IC2879,G,11:29:44.29,+09:00:49.9,Leo -IC2880,*,11:29:53.02,+13:11:55.8,Leo -IC2881,G,11:29:54.41,+12:30:40.4,Leo -IC2882,*,11:30:09.44,+11:59:20.9,Leo -IC2883,G,11:30:15.79,+10:54:39.3,Leo -IC2884,*Ass,11:27:41.05,-79:44:03.8,Cha -IC2885,*,11:30:22.61,+09:46:19.4,Leo -IC2886,G,11:30:24.44,+11:33:45.6,Leo -IC2887,Dup,11:30:29.72,+09:23:16.6,Leo -IC2888,Other,11:30:35.11,+09:54:30.6,Leo -IC2889,G,11:30:28.99,-13:05:27.6,Crt -IC2890,*,11:30:46.15,+13:10:54.6,Leo -IC2891,G,11:30:48.14,+12:40:39.8,Leo -IC2892,G,11:30:48.91,+10:35:18.8,Leo -IC2893,GPair,11:30:53.28,+13:23:27.7,Leo -IC2893 NED01,G,11:30:53.21,+13:23:31.1,Leo -IC2893 NED02,G,11:30:53.46,+13:23:23.7,Leo -IC2894,G,11:30:57.50,+13:14:07.3,Leo -IC2895,*,11:30:57.29,+09:58:36.7,Leo -IC2896,G,11:31:13.43,+12:21:00.2,Leo -IC2897,*,11:31:19.37,+11:32:56.6,Leo -IC2898,G,11:31:20.41,+13:20:10.4,Leo -IC2899,**,11:31:20.37,+10:38:05.0,Leo -IC2900,G,11:31:29.69,+13:10:02.3,Leo -IC2901,G,11:31:32.13,+12:41:59.0,Leo -IC2902,*,11:31:33.16,+14:13:21.9,Leo -IC2903,G,11:31:40.76,+12:38:33.3,Leo -IC2904,*,11:31:42.37,+13:11:02.9,Leo -IC2905,*,11:31:46.98,+09:06:25.1,Leo -IC2906,*,11:31:49.61,+13:07:58.4,Leo -IC2907,*,11:31:48.75,+09:53:58.0,Leo -IC2908,*,11:31:50.48,+12:56:16.4,Leo -IC2909,G,11:31:50.89,+11:28:12.8,Leo -IC2910,G,11:31:54.71,-09:43:31.3,Crt -IC2911,**,11:32:04.63,+12:58:38.5,Leo -IC2912,G,11:32:07.11,+11:42:35.1,Leo -IC2913,G,11:31:51.35,-30:24:38.8,Hya -IC2914,G,11:32:12.43,+13:29:32.9,Leo -IC2915,NonEx,,, -IC2916,*,11:32:16.15,+11:41:01.4,Leo -IC2917,G,11:32:19.33,+10:56:43.6,Leo -IC2918,*,11:32:26.23,+13:14:54.6,Leo -IC2919,G,11:32:34.90,+14:11:21.2,Leo -IC2920,*,11:32:48.67,+12:33:25.6,Leo -IC2921,G,11:32:49.28,+10:17:47.3,Leo -IC2922,*,11:32:51.22,+12:55:22.2,Leo -IC2923,G,11:32:53.55,+13:09:50.3,Leo -IC2924,*,11:32:52.19,+09:01:23.8,Leo -IC2925,G,11:33:13.23,+34:15:54.4,UMa -IC2926,*,11:33:04.07,+12:26:11.2,Leo -IC2927,*,11:33:04.77,+13:05:08.6,Leo -IC2928,G,11:33:29.96,+34:18:58.5,UMa -IC2929,G,11:33:31.47,+12:08:14.4,Leo -IC2930,G,11:33:44.14,+10:05:19.2,Leo -IC2931,*,11:33:50.49,+12:28:02.0,Leo -IC2932,**,11:33:53.71,+10:32:36.2,Leo -IC2933,G,11:34:12.75,+34:18:45.0,UMa -IC2934,G,11:34:19.55,+13:19:19.2,Leo -IC2935,*,11:34:48.21,+10:15:00.5,Leo -IC2936,G,11:34:56.82,+13:00:31.7,Leo -IC2937,*,11:35:03.37,+10:06:12.1,Leo -IC2938,G,11:35:36.30,+13:40:50.6,Leo -IC2939,*,11:35:37.98,+10:41:49.5,Leo -IC2940,Other,11:36:02.74,+21:57:42.0,Leo -IC2941,G,11:36:09.97,+10:03:20.1,Leo -IC2942,G,11:36:12.21,+11:48:57.2,Leo -IC2943,G,11:36:42.31,+54:50:45.7,UMa -IC2944,Cl+N,11:35:46.93,-63:01:11.4,Cen -IC2945,GPair,11:37:04.29,+12:55:35.7,Leo -IC2945 NED01,G,11:37:04.30,+12:55:35.2,Leo -IC2946,G,11:37:29.69,+32:15:08.9,UMa -IC2947,G,11:37:30.97,+31:21:44.4,UMa -IC2948,Cl+N,11:39:05.94,-63:26:37.9,Cen -IC2949,**,11:40:54.35,-46:27:16.5,Cen -IC2950,G,11:41:37.92,+37:59:31.6,UMa -IC2951,G,11:43:24.56,+19:44:59.3,Leo -IC2952,G,11:44:17.13,+33:21:04.9,UMa -IC2953,Dup,11:44:25.78,+33:21:18.3,UMa -IC2954,*,11:45:03.26,+26:47:11.4,Leo -IC2955,G,11:45:03.91,+19:37:14.2,Leo -IC2956,G,11:45:17.56,+26:46:02.6,Leo -IC2957,G,11:45:36.94,+31:17:58.4,UMa -IC2958,G,11:45:42.32,+33:09:16.0,UMa -IC2959,Dup,11:46:10.14,+33:06:31.5,UMa -IC2960,G,11:46:19.70,+35:00:13.5,UMa -IC2961,G,11:47:49.57,+31:20:41.1,UMa -IC2962,Other,11:49:05.99,-12:18:40.9,Crt -IC2963,Dup,11:49:24.53,-05:07:06.4,Vir -IC2964,Other,11:49:52.47,+12:03:01.1,Leo -IC2965,Dup,11:54:01.51,-19:34:08.0,Crt -IC2966,RfN,11:50:13.55,-64:52:22.6,Mus -IC2967,G,11:50:55.13,+30:51:02.5,UMa -IC2968,G,11:52:30.53,+20:37:31.7,Leo -IC2969,G,11:52:31.27,-03:52:20.1,Vir -IC2970,Other,11:53:09.75,-23:07:23.8,Crt -IC2971,G,11:53:27.54,+30:41:48.9,UMa -IC2972,Dup,11:53:40.63,-03:59:47.5,Vir -IC2973,G,11:53:50.76,+33:21:55.8,UMa -IC2974,G,11:53:48.73,-05:10:04.2,Vir -IC2975,Dup,11:53:48.73,-05:10:04.2,Vir -IC2976,Dup,11:56:01.05,-02:43:15.1,Vir -IC2977,G,11:55:14.66,-37:41:46.7,Cen -IC2978,G,11:56:23.23,+32:02:19.4,UMa -IC2979,G,11:56:54.24,+32:09:31.7,UMa -IC2980,G,11:57:30.16,-73:41:04.2,Mus -IC2981,G,11:55:42.62,+32:11:20.0,UMa -IC2982,Dup,11:57:51.38,+27:52:07.2,Leo -IC2983,Other,11:58:16.73,-02:06:36.2,Vir -IC2984,G,11:59:07.23,+30:41:49.0,UMa -IC2985,G,11:59:12.75,+30:43:52.4,UMa -IC2986,G,11:59:49.61,+30:50:39.9,UMa -IC2987,G,12:03:24.54,+38:48:47.9,UMa -IC2988,Other,12:03:42.20,+03:25:45.3,Vir -IC2989,Dup,12:04:34.03,+01:48:05.8,Vir -IC2990,G,12:04:38.60,+11:03:00.2,Vir -IC2991,G,12:05:12.51,+10:38:25.3,Vir -IC2992,G,12:05:15.83,+30:51:20.3,UMa -IC2993,G,12:05:38.33,+32:49:19.9,UMa -IC2994,G,12:05:27.87,+12:42:10.4,Vir -IC2995,G,12:05:46.91,-27:56:24.5,Hya -IC2996,G,12:05:48.63,-29:58:18.9,Hya -IC2997,Other,12:05:44.47,+20:16:51.1,Com -IC2998,Other,12:05:55.24,+20:45:12.1,Com -IC2999,G,12:05:57.54,+31:20:54.6,UMa -IC3000,Other,12:06:08.55,-29:40:24.2,Hya -IC3001,G,12:06:16.81,+33:31:33.1,UMa -IC3002,G,12:07:04.19,+33:22:58.2,CVn -IC3003,G,12:07:32.62,+32:48:46.8,Com -IC3004,G,12:07:10.24,+13:14:51.1,Vir -IC3005,G,12:07:14.17,-30:01:28.7,Hya -IC3006,Other,12:07:24.34,+12:59:36.2,Vir -IC3007,G,12:07:30.78,+31:20:53.2,Com -IC3008,G,12:07:51.80,+13:34:38.3,Com -IC3009,Other,12:08:00.11,+12:38:47.3,Vir -IC3010,G,12:07:57.41,-30:20:22.1,Hya -IC3011,Dup,12:08:09.62,+10:22:44.0,Vir -IC3012,G,12:08:23.94,+11:10:35.8,Vir -IC3013,G,12:08:25.58,+10:01:00.0,Vir -IC3014,G,12:08:37.00,+38:49:54.4,CVn -IC3015,G,12:09:00.28,-31:31:11.6,Hya -IC3016,G,12:09:18.54,+11:25:48.8,Vir -IC3017,G,12:09:22.83,+13:37:05.4,Com -IC3018,G,12:09:24.96,+13:34:28.0,Com -IC3019,G,12:09:22.26,+13:59:32.7,Com -IC3020,G,12:09:27.22,+14:13:28.2,Com -IC3021,G,12:09:54.57,+13:02:59.9,Vir -IC3022,G,12:10:02.38,+38:44:23.9,CVn -IC3023,G,12:10:01.75,+14:22:00.7,Com -IC3024,G,12:10:11.93,+12:19:32.4,Vir -IC3025,G,12:10:23.09,+10:11:18.8,Vir -IC3026,Other,12:10:34.30,-29:55:23.6,Hya -IC3027,Other,12:10:30.07,+14:11:36.7,Com -IC3028,G,12:10:35.69,+11:45:38.9,Vir -IC3029,G,12:10:41.86,+13:19:52.6,Com -IC3030,Other,12:11:06.03,+14:08:36.8,Com -IC3031,G,12:11:04.18,+13:18:30.4,Com -IC3032,G,12:11:07.76,+14:16:29.3,Com -IC3033,G,12:11:09.95,+13:35:15.0,Com -IC3034,G,12:11:47.80,+14:12:04.1,Com -IC3035,Dup,12:12:11.80,+13:14:47.5,Vir -IC3036,G,12:12:15.11,+12:29:18.2,Vir -IC3037,G,12:12:20.48,+09:59:11.2,Vir -IC3038,G,12:12:32.61,+11:21:10.2,Vir -IC3039,G,12:12:32.57,+12:18:35.6,Vir -IC3040,G,12:12:34.53,+11:04:30.1,Vir -IC3041,G,12:12:42.66,+12:45:46.2,Vir -IC3042,Dup,12:12:46.45,+10:51:57.5,Vir -IC3043,G,12:12:47.18,+10:00:34.8,Vir -IC3044,G,12:12:48.52,+13:58:35.3,Com -IC3045,Other,12:12:59.67,+12:46:46.2,Vir -IC3046,G,12:13:07.87,+12:55:05.6,Vir -IC3047,G,12:13:14.63,+12:59:51.6,Vir -IC3048,*,12:13:21.44,+13:04:08.5,Vir -IC3049,G,12:13:33.70,+14:28:49.0,Com -IC3050,Dup,12:13:47.27,+13:25:29.3,Com -IC3051,Dup,12:13:53.59,+13:10:22.3,Vir -IC3052,G,12:13:48.27,+12:41:26.0,Vir -IC3053,G,12:13:51.93,+14:13:22.7,Com -IC3054,G,12:14:14.43,+13:32:34.6,Com -IC3055,GTrpl,12:14:22.30,+12:05:29.0,Vir -IC3055 NED01,G,12:14:21.87,+12:05:26.0,Vir -IC3055 NED02,G,12:14:22.11,+12:05:30.8,Vir -IC3055 NED03,G,12:14:22.61,+12:05:29.9,Vir -IC3056,G,12:14:36.94,+12:48:42.7,Vir -IC3057,Other,12:15:02.67,-44:28:22.7,Cen -IC3058,G,12:14:47.45,+14:05:43.8,Com -IC3059,G,12:14:55.02,+13:27:37.9,Com -IC3060,G,12:15:02.09,+12:32:49.6,Vir -IC3061,G,12:15:04.44,+14:01:44.3,Com -IC3062,G,12:15:05.37,+13:35:41.1,Com -IC3063,G,12:15:06.73,+12:01:00.2,Vir -IC3064,Dup,12:15:16.81,+13:01:26.3,Vir -IC3065,G,12:15:12.56,+14:25:58.4,Com -IC3066,G,12:15:16.58,+13:28:23.3,Com -IC3067,Dup,12:15:15.89,+23:57:29.4,Com -IC3068,G,12:15:23.20,+11:30:39.2,Vir -IC3069,G,12:15:19.87,+10:09:38.9,Vir -IC3070,*,12:15:24.67,+13:02:22.1,Vir -IC3071,*,12:15:31.86,+09:32:44.0,Vir -IC3072,*,12:15:38.13,+09:33:20.3,Vir -IC3073,G,12:15:35.60,+13:37:09.0,Com -IC3074,G,12:15:46.19,+10:41:57.1,Vir -IC3075,G,12:15:55.09,+23:35:44.0,Com -IC3076,*,12:16:03.98,+09:04:44.2,Vir -IC3077,G,12:15:56.34,+14:25:58.9,Com -IC3078,G,12:16:00.04,+12:41:14.3,Vir -IC3079,G,12:16:04.13,+11:32:05.5,Vir -IC3080,G,12:16:02.67,+14:11:21.8,Com -IC3081,G,12:16:09.06,+12:41:28.7,Vir -IC3082,G,12:16:12.10,+23:50:31.5,Com -IC3083,G,12:16:21.24,+12:31:40.0,Vir -IC3084,G,12:16:23.46,+23:55:04.4,Com -IC3085,*,12:16:26.01,+09:28:08.4,Vir -IC3086,**,12:16:27.74,+09:00:33.0,Vir -IC3087,**,12:16:26.50,+13:17:16.0,Vir -IC3088,*,12:16:28.36,+09:27:31.7,Vir -IC3089,G,12:16:29.70,+23:49:39.8,Com -IC3090,**,12:16:31.61,+09:26:22.0,Vir -IC3091,G,12:16:29.17,+14:00:44.5,Com -IC3092,G,12:16:32.29,+10:02:47.1,Vir -IC3093,G,12:16:42.36,+14:16:40.1,Com -IC3094,G,12:16:56.01,+13:37:31.5,Com -IC3095,G,12:16:55.54,+23:57:28.5,Com -IC3096,G,12:16:52.36,+14:30:52.5,Com -IC3097,G,12:17:01.11,+09:24:27.2,Vir -IC3098,Dup,12:17:09.88,+07:11:29.7,Vir -IC3099,G,12:17:09.27,+12:27:14.5,Vir -IC3100,G,12:17:05.55,+12:17:23.4,Vir -IC3101,G,12:17:19.65,+11:56:36.5,Vir -IC3102,Dup,12:17:25.81,+06:41:24.3,Vir -IC3103,*,12:17:28.45,+09:21:37.6,Vir -IC3104,G,12:18:46.06,-79:43:33.8,Cha -IC3105,G,12:17:33.75,+12:23:17.2,Vir -IC3106,*,12:17:45.81,+09:36:47.3,Vir -IC3107,G,12:17:46.93,+10:50:40.7,Vir -IC3108,G,12:17:42.70,+13:22:47.7,Com -IC3109,G,12:17:44.11,+13:10:15.7,Vir -IC3110,G,12:17:44.84,+37:23:58.9,CVn -IC3111,G,12:17:50.81,+08:25:49.0,Vir -IC3112,G,12:17:48.36,+26:01:50.6,Com -IC3113,Dup,12:17:58.12,+07:11:09.3,Vir -IC3114,*,12:17:56.75,+09:08:07.4,Vir -IC3115,Dup,12:17:59.90,+06:39:15.1,Vir -IC3116,G,12:17:57.17,+25:04:35.5,Com -IC3117,**,12:18:04.66,+09:04:35.5,Vir -IC3118,G,12:18:11.05,+09:29:59.3,Vir -IC3119,G,12:18:08.48,+24:41:17.9,Com -IC3120,G,12:18:15.35,+13:44:56.9,Com -IC3121,GPair,12:18:17.50,+13:15:26.0,Vir -IC3121 NED01,G,12:18:17.69,+13:15:23.2,Vir -IC3121 NED02,G,12:18:17.36,+13:15:29.8,Vir -IC3122,G,12:18:21.42,+25:13:00.5,Com -IC3123,*,12:18:27.59,+08:03:53.7,Vir -IC3124,*,12:18:27.53,+09:35:18.2,Vir -IC3125,*,12:18:25.47,+24:21:54.4,Com -IC3126,G,12:18:37.16,+13:48:54.6,Com -IC3127,G,12:18:35.23,+11:52:13.5,Vir -IC3128,GTrpl,12:18:40.20,+11:44:01.0,Vir -IC3128A,G,12:18:41.87,+11:43:54.6,Vir -IC3128B,G,12:18:38.56,+11:44:06.1,Vir -IC3129,*,12:18:44.96,+09:35:26.4,Vir -IC3130,**,12:18:49.60,+08:13:58.7,Vir -IC3131,G,12:18:50.89,+07:51:42.8,Vir -IC3132,Dup,12:18:50.89,+07:51:42.8,Vir -IC3133,Other,12:18:54.56,+07:38:22.8,Vir -IC3134,G,12:18:56.12,+08:57:41.8,Vir -IC3135,G,12:18:52.58,+27:29:29.4,Com -IC3136,G,12:18:57.37,+06:11:03.8,Vir -IC3137,G,12:18:54.67,+12:28:12.2,Vir -IC3138,GPair,12:18:56.20,+12:26:38.0,Vir -IC3138 NED01,G,12:18:56.16,+12:26:43.0,Vir -IC3138 NED02,G,12:18:56.20,+12:26:31.6,Vir -IC3139,*,12:19:00.76,+09:07:36.4,Vir -IC3140,*,12:18:57.88,+27:07:46.1,Com -IC3141,G,12:18:58.44,+24:11:10.6,Com -IC3142,GPair,12:19:03.50,+13:58:53.0,Com -IC3142 NED01,G,12:19:01.98,+13:58:56.8,Com -IC3142 NED02,G,12:19:05.15,+13:58:49.9,Com -IC3143,G,12:19:05.39,+27:17:54.3,Com -IC3144,G,12:19:09.76,+25:17:49.1,Com -IC3145,*,12:19:10.49,+24:17:38.9,Com -IC3146,G,12:19:12.50,+25:42:54.1,Com -IC3147,GPair,12:19:17.90,+12:01:02.0,Vir -IC3147 NED01,G,12:19:17.19,+12:00:58.3,Vir -IC3147 NED02,G,12:19:18.67,+12:01:05.5,Vir -IC3148,G,12:19:21.62,+07:52:13.2,Vir -IC3149,G,12:19:24.21,+12:18:05.0,Vir -IC3150,G,12:19:28.49,+07:47:53.6,Vir -IC3151,G,12:19:32.88,+09:24:51.2,Vir -IC3152,G,12:19:35.99,-26:08:43.8,Hya -IC3153,G,12:19:36.84,+05:23:52.1,Vir -IC3154,G,12:19:33.99,+25:35:09.3,Com -IC3155,G,12:19:45.31,+06:00:20.7,Vir -IC3156,G,12:19:44.14,+09:08:54.9,Vir -IC3157,G,12:19:47.92,+12:25:19.0,Vir -IC3158,**,12:19:49.86,+09:17:23.1,Vir -IC3159,G,12:19:53.21,+11:40:28.1,Vir -IC3160,*,12:19:59.85,+09:06:06.8,Vir -IC3161,*,12:20:01.21,+08:59:56.6,Vir -IC3162,*,12:20:03.13,+08:59:49.1,Vir -IC3163,**,12:20:05.75,+09:15:20.2,Vir -IC3164,**,12:20:04.89,+24:57:21.3,Com -IC3165,G,12:20:04.77,+27:58:31.2,Com -IC3166,Other,12:19:54.00,+60:41:39.4,UMa -IC3167,G,12:20:18.78,+09:32:43.3,Vir -IC3168,G,12:20:18.54,+27:55:13.4,Com -IC3169,G,12:20:21.39,+25:35:58.2,Com -IC3170,G,12:20:26.55,+09:25:27.3,Vir -IC3171,G,12:20:24.06,+25:33:38.1,Com -IC3172,G,12:20:24.55,+27:49:07.6,Com -IC3173,G,12:20:30.18,+11:20:27.4,Vir -IC3174,G,12:20:29.53,+10:14:42.6,Vir -IC3175,G,12:20:33.35,+09:51:12.3,Vir -IC3176,G,12:20:30.08,+25:30:55.5,Com -IC3177,Other,12:20:34.33,+14:07:39.4,Com -IC3178,*,12:20:35.06,+26:10:09.5,Com -IC3179,G,12:20:37.73,+26:09:55.4,Com -IC3180,Other,12:20:23.74,+60:41:39.6,UMa -IC3181,Dup,12:20:42.09,+29:20:45.2,Com -IC3182,**,12:20:47.76,+12:43:48.5,Vir -IC3183,**,12:20:48.87,+06:41:12.2,Vir -IC3184,G,12:20:46.80,+24:54:56.1,Com -IC3185,G,12:20:52.68,+25:25:46.9,Com -IC3186,G,12:20:55.88,+24:40:06.9,Com -IC3187,G,12:20:54.68,+11:09:42.5,Vir -IC3188,G,12:20:55.10,+11:00:32.0,Vir -IC3189,G,12:20:56.32,+25:25:35.5,Com -IC3190,*,12:21:02.41,+09:34:11.2,Vir -IC3191,*,12:21:05.15,+07:42:15.9,Vir -IC3192,G,12:21:04.81,+11:45:16.6,Vir -IC3193,G,12:21:01.28,+27:53:55.6,Com -IC3194,G,12:21:09.00,+25:08:00.6,Com -IC3195,G,12:21:17.43,+25:48:29.8,Com -IC3196,G,12:21:26.63,+11:45:28.0,Vir -IC3197,*,12:21:25.90,+25:26:38.0,Com -IC3198,*,12:21:31.14,+26:21:58.7,Com -IC3199,G,12:21:45.60,+10:35:44.3,Vir -IC3200,G,12:21:37.19,+26:45:38.8,Com -IC3201,G,12:21:40.37,+25:43:33.6,Com -IC3202,G,12:21:44.33,+27:03:25.6,Com -IC3203,G,12:21:45.63,+25:53:04.8,Com -IC3204,G,12:21:50.43,+24:14:56.8,Com -IC3205,G,12:21:50.94,+26:20:27.6,Com -IC3206,G,12:21:51.31,+26:21:48.9,Com -IC3207,G,12:21:52.23,+24:21:16.4,Com -IC3208,G,12:21:55.58,+11:58:00.6,Vir -IC3209,G,12:22:06.15,+11:45:17.0,Vir -IC3210,G,12:22:00.98,+28:25:51.9,Com -IC3211,Dup,12:22:07.32,+08:59:26.0,Vir -IC3212,G,12:22:03.40,+28:11:09.6,Com -IC3213,G,12:22:07.67,+23:52:10.6,Com -IC3214,*,12:22:09.11,+27:14:06.3,Com -IC3215,G,12:22:10.38,+26:03:07.0,Com -IC3216,G,12:22:11.81,+25:17:12.1,Com -IC3217,G,12:22:13.07,+26:23:16.9,Com -IC3218,G,12:22:19.52,+06:55:40.5,Vir -IC3219,G,12:22:15.03,+25:57:04.8,Com -IC3220,G,12:22:21.59,+10:36:07.1,Vir -IC3221,G,12:22:20.15,+25:17:01.6,Com -IC3222,G,12:22:19.47,+28:49:53.7,Com -IC3223,Other,12:22:30.61,+09:29:14.1,Vir -IC3224,G,12:22:35.91,+12:09:28.9,Vir -IC3225,G,12:22:38.99,+06:40:37.4,Vir -IC3226,*,12:22:34.95,+26:04:02.4,Com -IC3227,G,12:22:35.66,+24:05:06.5,Com -IC3228,G,12:22:39.38,+24:19:48.3,Com -IC3229,G,12:22:52.80,+06:40:47.5,Vir -IC3230,G,12:22:39.68,+27:44:49.1,Com -IC3231,G,12:22:43.78,+24:49:13.7,Com -IC3232,*,12:22:47.79,+24:25:28.3,Com -IC3233,G,12:22:54.89,+12:34:00.4,Vir -IC3234,G,12:22:52.16,+28:06:45.1,Com -IC3235,G,12:22:57.90,+13:32:44.8,Com -IC3236,G,12:23:00.23,+10:06:04.4,Vir -IC3237,G,12:22:58.01,+28:29:39.0,Com -IC3238,G,12:23:06.38,+14:27:30.0,Com -IC3239,G,12:23:09.57,+11:43:33.6,Vir -IC3240,G,12:23:07.32,+10:21:44.2,Vir -IC3241,G,12:23:08.44,+26:54:19.0,Com -IC3242,G,12:23:10.44,+26:14:56.0,Com -IC3243,G,12:23:11.33,+27:45:56.5,Com -IC3244,G,12:23:12.26,+14:23:20.3,Com -IC3245,Other,12:23:17.71,+09:07:46.4,Vir -IC3246,G,12:23:17.15,+13:03:06.6,Vir -IC3247,G,12:23:13.99,+28:53:37.5,Com -IC3248,*,12:23:16.88,+25:33:07.3,Com -IC3249,G,12:23:17.93,+25:26:40.8,Com -IC3250,*,12:23:17.82,+25:37:43.7,Com -IC3251,*,12:23:18.89,+25:39:12.2,Com -IC3252,**,12:23:24.92,+28:37:05.6,Com -IC3253,G,12:23:45.22,-34:37:19.9,Cen -IC3254,Dup,12:23:29.83,+19:25:36.9,Com -IC3255,G,12:23:34.74,+09:38:55.0,Vir -IC3256,Dup,12:23:39.00,+07:03:14.4,Vir -IC3257,Other,12:23:44.71,+07:15:13.6,Vir -IC3258,G,12:23:44.47,+12:28:42.0,Vir -IC3259,G,12:23:48.52,+07:11:12.6,Vir -IC3260,Dup,12:23:53.56,+07:06:25.6,Vir -IC3261,G,12:23:52.50,+11:28:52.7,Vir -IC3262,G,12:23:48.17,+27:23:39.4,Com -IC3263,G,12:23:50.59,+28:11:58.2,Com -IC3264,G,12:23:51.95,+25:33:26.6,Com -IC3265,*,12:23:58.83,+07:48:13.6,Vir -IC3266,Dup,12:24:00.26,+07:47:06.7,Vir -IC3267,G,12:24:05.53,+07:02:28.6,Vir -IC3268,G,12:24:07.44,+06:36:26.9,Vir -IC3269,G,12:24:04.40,+27:26:05.2,Com -IC3270,G,12:24:05.85,+27:34:40.4,Com -IC3271,G,12:24:13.93,+07:57:10.7,Vir -IC3272,G,12:24:09.31,+23:17:04.7,Com -IC3273,Dup,12:24:14.53,+08:32:09.1,Vir -IC3274,Dup,12:24:14.72,+09:16:00.7,Vir -IC3275,G,12:24:19.48,+10:26:46.8,Vir -IC3276,G,12:24:14.10,+25:49:07.6,Com -IC3277,Other,12:24:15.71,+25:33:50.4,Com -IC3278,GTrpl,12:24:14.96,+27:25:14.9,Com -IC3278 NED01,G,12:24:14.35,+27:25:24.5,Com -IC3278 NED02,G,12:24:15.06,+27:25:23.7,Com -IC3278 NED03,G,12:24:15.44,+27:25:06.5,Com -IC3279,**,12:24:24.03,+12:51:08.8,Vir -IC3280,G,12:24:26.74,+13:14:00.4,Vir -IC3281,Other,12:24:27.91,+07:49:08.9,Vir -IC3282,*,12:24:28.05,+25:40:14.3,Com -IC3283,G,12:24:28.02,+27:12:40.5,Com -IC3284,G,12:24:37.56,+10:50:20.5,Vir -IC3285,*,12:24:33.49,+24:51:34.8,Com -IC3286,G,12:24:34.48,+23:44:52.4,Com -IC3287,G,12:24:36.96,+24:35:41.0,Com -IC3288,G,12:24:39.41,+24:56:59.5,Com -IC3289,G,12:24:57.45,-26:01:50.6,Hya -IC3290,G,12:25:08.96,-39:46:31.8,Cen -IC3291,G,12:24:48.41,+12:01:06.6,Vir -IC3292,G,12:24:48.36,+18:11:42.4,Com -IC3293,G,12:24:53.51,+17:25:56.7,Com -IC3294,G,12:24:49.73,+25:35:50.9,Com -IC3295,Other,12:24:48.98,+28:42:28.2,Com -IC3296,G,12:24:57.84,+24:22:58.3,Com -IC3297,*,12:24:58.14,+26:46:16.3,Com -IC3298,G,12:25:03.80,+17:00:55.2,Com -IC3299,G,12:25:03.14,+27:22:28.1,Com -IC3300,G,12:25:04.98,+25:57:27.2,Com -IC3301,G,12:25:17.54,+14:10:21.2,Com -IC3302,G,12:25:10.43,+25:52:47.3,Com -IC3303,G,12:25:15.20,+12:42:52.6,Vir -IC3304,G,12:25:11.72,+25:25:27.4,Com -IC3305,G,12:25:14.50,+11:50:58.6,Vir -IC3306,G,12:25:12.48,+27:24:08.5,Com -IC3307,Dup,12:25:17.54,+14:10:21.2,Com -IC3308,G,12:25:18.21,+26:42:54.4,Com -IC3309,G,12:25:20.13,+28:22:51.6,Com -IC3310,*,12:25:55.30,+15:40:49.9,Com -IC3311,G,12:25:33.11,+12:15:37.2,Vir -IC3312,G,12:25:29.90,+23:34:53.9,Com -IC3313,G,12:25:36.44,+15:49:47.4,Com -IC3314,G,12:25:31.49,+23:35:27.8,Com -IC3315,G,12:25:38.93,+12:18:49.5,Vir -IC3316,G,12:25:36.15,+26:09:47.7,Com -IC3317,G,12:25:38.91,+25:20:38.1,Com -IC3318,*,12:25:49.85,+09:45:46.1,Vir -IC3319,Other,12:25:50.93,+10:23:27.6,Vir -IC3320,Dup,12:25:50.67,+10:27:32.6,Vir -IC3321,G,12:25:46.18,+26:04:57.1,Com -IC3322,G,12:25:54.10,+07:33:17.2,Vir -IC3323,*,12:25:48.08,+27:32:32.6,Com -IC3324,G,12:25:49.16,+26:44:23.6,Com -IC3325,G,12:25:51.47,+23:53:45.3,Com -IC3326,G,12:25:52.75,+23:46:06.4,Com -IC3327,G,12:26:02.78,+14:52:49.3,Com -IC3328,G,12:25:57.93,+10:03:13.6,Vir -IC3329,HII,12:25:55.92,+27:33:50.8,Com -IC3330,G,12:25:56.31,+30:50:36.9,Com -IC3331,G,12:26:05.32,+11:48:44.0,Vir -IC3332,G,12:26:05.19,+25:16:47.4,Com -IC3333,*,12:26:08.77,+13:07:58.6,Vir -IC3334,G,12:26:09.53,+28:27:57.0,Com -IC3335,G,12:26:19.13,+26:07:45.5,Com -IC3336,G,12:26:19.89,+26:50:18.4,Com -IC3337,G,12:26:21.47,+25:18:41.5,Com -IC3338,G,12:26:22.28,+25:53:10.4,Com -IC3339,Dup,12:26:30.10,+08:52:20.0,Vir -IC3340,G,12:26:32.71,+16:50:40.8,Com -IC3341,G,12:26:23.19,+27:44:43.9,Com -IC3342,*,12:26:27.37,+27:08:19.1,Com -IC3343,*,12:26:35.10,+08:52:27.4,Vir -IC3344,G,12:26:32.39,+13:34:43.6,Com -IC3345,G,12:26:33.34,+24:22:07.9,Com -IC3346,G,12:26:44.43,+11:22:47.0,Vir -IC3347,G,12:26:44.57,+10:55:06.9,Vir -IC3348,G,12:26:38.11,+25:37:29.6,Com -IC3349,G,12:26:47.06,+12:27:14.3,Vir -IC3350,**,12:26:46.43,+09:26:33.4,Vir -IC3351,*,12:26:40.99,+27:36:20.1,Com -IC3352,Other,12:26:47.80,+08:45:27.0,Vir -IC3353,G,12:26:45.08,+27:54:44.4,Com -IC3354,*,12:26:51.46,+12:05:49.7,Vir -IC3355,G,12:26:51.14,+13:10:32.6,Vir -IC3356,G,12:26:50.49,+11:33:32.5,Vir -IC3357,G,12:26:51.35,+09:46:39.0,Vir -IC3358,G,12:26:54.34,+11:39:50.3,Vir -IC3359,G,12:26:51.40,+23:29:53.4,Com -IC3360,*,12:26:50.72,+26:02:47.9,Com -IC3361,G,12:26:54.55,+10:39:57.1,Vir -IC3362,G,12:26:54.41,+26:41:24.3,Com -IC3363,G,12:27:03.07,+12:33:38.9,Vir -IC3364,G,12:27:04.91,+25:33:47.7,Com -IC3365,G,12:27:11.18,+15:53:48.0,Com -IC3366,G,12:27:12.12,+09:24:36.7,Vir -IC3367,GPair,12:27:11.90,+26:57:24.3,Com -IC3367 NED01,G,12:27:10.07,+26:57:26.7,Com -IC3368,G,12:27:20.38,+16:25:42.9,Com -IC3369,G,12:27:16.94,+16:01:28.1,Com -IC3370,G,12:27:37.33,-39:20:16.0,Cen -IC3371,G,12:27:22.25,+10:52:00.4,Vir -IC3372,G,12:27:24.54,+25:17:14.0,Com -IC3373,G,12:27:27.81,+25:27:14.1,Com -IC3374,G,12:27:33.48,+10:00:13.6,Vir -IC3375,*,12:27:40.36,+27:21:53.1,Com -IC3376,G,12:27:50.34,+26:59:36.7,Com -IC3377,G,12:27:51.95,+24:56:31.8,Com -IC3378,G,12:28:01.51,+17:17:46.7,Com -IC3379,G,12:28:04.26,+17:18:20.7,Com -IC3380,G,12:28:05.53,+26:40:22.3,Com -IC3381,G,12:28:14.88,+11:47:23.4,Vir -IC3382,G,12:28:13.54,+13:34:14.5,Com -IC3383,G,12:28:12.32,+10:17:51.6,Vir -IC3384,G,12:28:12.26,+25:05:28.8,Com -IC3385,G,12:28:14.95,+25:25:57.4,Com -IC3386,G,12:28:23.68,+13:11:44.7,Vir -IC3387,G,12:28:18.82,+27:59:44.6,Com -IC3388,G,12:28:28.06,+12:49:25.2,Vir -IC3389,G,12:28:23.52,+27:50:41.8,Com -IC3390,G,12:28:28.57,+24:48:33.6,Com -IC3391,G,12:28:27.30,+18:24:54.1,Com -IC3392,G,12:28:43.26,+14:59:58.2,Com -IC3393,G,12:28:41.71,+12:54:57.3,Vir -IC3394,G,12:28:41.21,+26:47:54.3,Com -IC3395,G,12:28:44.50,+25:02:05.1,Com -IC3396,G,12:28:44.98,+25:03:00.8,Com -IC3397,G,12:28:46.56,+25:43:54.9,Com -IC3398,NonEx,,, -IC3399,*,12:28:55.98,+25:41:45.9,Com -IC3400,*,12:29:02.90,+09:24:22.2,Vir -IC3401,G,12:28:58.84,+26:27:36.3,Com -IC3402,G,12:28:59.32,+28:51:43.0,Com -IC3403,G,12:29:01.46,+24:37:57.6,Com -IC3404,Other,12:29:10.72,+07:09:14.2,Vir -IC3405,G,12:28:59.61,+37:43:48.4,CVn -IC3406,G,12:29:02.63,+27:38:25.9,Com -IC3407,G,12:29:03.86,+27:46:43.9,Com -IC3408,*,12:29:15.79,+11:52:33.2,Vir -IC3409,G,12:29:21.23,+14:47:21.5,Com -IC3410,G,12:29:06.17,+19:00:17.2,Com -IC3411,G,12:29:12.39,+24:35:03.1,Com -IC3412,G,12:29:22.65,+09:59:20.5,Vir -IC3413,G,12:29:22.51,+11:26:02.0,Vir -IC3414,G,12:29:28.78,+06:46:18.5,Vir -IC3415,*,12:29:21.61,+26:45:58.4,Com -IC3416,G,12:29:34.96,+10:47:35.1,Vir -IC3417,*,12:29:39.22,+07:51:40.6,Vir -IC3418,G,12:29:43.92,+11:24:16.9,Vir -IC3419,G,12:29:44.58,+15:01:29.1,Com -IC3420,*,12:29:42.56,+13:26:47.1,Com -IC3421,G,12:29:38.59,+26:13:50.2,Com -IC3422,G,12:29:54.61,+14:41:18.1,Com -IC3423,*,12:29:46.52,+13:39:31.6,Com -IC3424,G,12:29:45.04,+24:24:31.2,Com -IC3425,G,12:29:56.39,+10:36:55.2,Vir -IC3426,*,12:30:01.46,+13:35:53.4,Com -IC3427,Dup,12:30:10.33,+10:46:46.1,Vir -IC3428,*,12:30:07.51,+23:40:30.1,Com -IC3429,G,12:30:07.94,+23:32:42.5,Com -IC3430,G,12:30:16.88,+09:05:06.4,Vir -IC3431,G,12:30:24.18,+11:36:52.1,Vir -IC3432,G,12:30:27.84,+14:09:36.7,Com -IC3433,G,12:30:28.21,+17:18:35.0,Com -IC3434,G,12:30:27.26,+18:48:35.0,Com -IC3435,G,12:30:39.88,+15:07:46.9,Com -IC3436,G,12:30:29.93,+19:40:22.9,Com -IC3437,G,12:30:45.91,+11:20:35.5,Vir -IC3438,Dup,12:30:59.71,+08:04:40.3,Vir -IC3439,G,12:30:59.47,+25:33:42.5,Com -IC3440,G,12:31:05.16,+12:01:47.5,Vir -IC3441,G,12:31:04.42,+28:51:09.7,Com -IC3442,G,12:31:20.19,+14:06:54.7,Com -IC3443,G,12:31:15.73,+12:19:54.4,Vir -IC3444,**,12:31:13.89,+27:32:58.7,Com -IC3445,G,12:31:19.43,+12:44:16.8,Vir -IC3446,G,12:31:22.94,+11:29:32.6,Vir -IC3447,G,12:31:17.90,+10:40:48.6,Vir -IC3448,G,12:31:23.19,+17:12:23.1,Com -IC3449,G,12:31:22.94,+25:54:50.4,Com -IC3450,G,12:31:24.82,+26:47:46.1,Com -IC3451,G,12:31:24.07,+28:51:18.7,Com -IC3452,Dup,12:31:32.53,+11:37:29.0,Vir -IC3453,G,12:31:37.74,+14:51:35.3,Com -IC3454,G,12:31:38.65,+27:29:44.6,Com -IC3455,G,12:31:44.55,+25:47:09.9,Com -IC3456,Other,12:31:43.75,+28:21:25.8,Com -IC3457,G,12:31:51.34,+12:39:25.2,Vir -IC3458,G,12:31:44.00,+28:08:50.5,Com -IC3459,G,12:31:55.92,+12:10:26.5,Vir -IC3460,G,12:31:50.39,+27:23:12.8,Com -IC3461,G,12:32:02.74,+11:53:24.3,Vir -IC3462,G,12:32:09.58,+15:18:03.4,Com -IC3463,Other,12:32:04.58,+12:19:09.9,Vir -IC3464,*,12:32:00.20,+26:00:17.4,Com -IC3465,G,12:32:12.24,+12:03:41.6,Vir -IC3466,G,12:32:05.68,+11:49:04.5,Vir -IC3467,G,12:32:24.57,+11:47:15.3,Vir -IC3468,G,12:32:14.21,+10:15:05.3,Vir -IC3469,G,12:32:10.98,+25:48:10.0,Com -IC3470,G,12:32:23.39,+11:15:46.7,Vir -IC3471,G,12:32:22.82,+16:01:07.8,Com -IC3472,G,12:32:18.78,+24:43:41.7,Com -IC3473,G,12:32:19.12,+18:14:38.9,Com -IC3474,G,12:32:36.51,+02:39:41.5,Vir -IC3475,G,12:32:41.06,+12:46:15.6,Vir -IC3476,G,12:32:41.88,+14:03:01.6,Com -IC3477,*,12:32:38.21,+26:02:18.4,Com -IC3478,G,12:32:44.20,+14:11:46.3,Com -IC3479,G,12:32:40.93,+25:24:22.0,Com -IC3480,**,12:32:41.49,+26:49:42.8,Com -IC3481,G,12:32:52.26,+11:24:15.8,Vir -IC3482,G,12:33:01.00,+27:49:49.1,Com -IC3483,G,12:33:10.06,+11:20:50.4,Vir -IC3484,G,12:33:05.30,+17:24:10.8,Com -IC3485,*,12:33:11.26,+09:13:02.2,Vir -IC3486,G,12:33:14.01,+12:51:28.2,Vir -IC3487,G,12:33:13.44,+09:23:50.5,Vir -IC3488,G,12:33:08.45,+26:20:57.7,Com -IC3489,G,12:33:13.70,+12:14:49.2,Vir -IC3490,G,12:33:13.90,+10:55:42.7,Vir -IC3491,G,12:33:08.99,+27:05:39.8,Com -IC3492,G,12:33:19.78,+12:51:12.4,Vir -IC3493,*,12:33:18.88,+09:23:35.5,Vir -IC3494,G,12:33:13.71,+27:35:02.8,Com -IC3495,**,12:33:16.29,+26:48:31.7,Com -IC3496,*,12:33:19.31,+26:45:19.6,Com -IC3497,*,12:33:28.54,+25:29:19.5,Com -IC3498,G,12:33:28.97,+26:44:17.0,Com -IC3499,G,12:33:45.00,+10:59:44.6,Vir -IC3500,G,12:33:49.67,+13:57:46.0,Com -IC3501,G,12:33:51.62,+13:19:20.8,Com -IC3502,G,12:33:42.33,+26:37:02.5,Com -IC3503,*,12:33:48.27,+37:47:21.5,CVn -IC3504,*,12:34:07.85,+06:53:10.9,Vir -IC3505,G,12:34:10.31,+15:58:05.6,Com -IC3506,G,12:34:06.74,+12:44:29.8,Vir -IC3507,G,12:34:04.45,+25:21:46.2,Com -IC3508,G,12:34:06.95,+26:40:14.8,Com -IC3509,G,12:34:11.53,+12:02:56.3,Vir -IC3510,G,12:34:14.81,+11:04:17.5,Vir -IC3511,*,12:34:09.49,+27:20:55.1,Com -IC3512,*,12:34:09.66,+27:21:40.0,Com -IC3513,*,12:34:11.61,+27:19:50.0,Com -IC3514,**,12:34:15.79,+26:42:02.2,Com -IC3515,G,12:34:16.10,+27:51:44.1,Com -IC3516,G,12:34:17.14,+27:27:08.3,Com -IC3517,G,12:34:30.76,+09:09:17.1,Vir -IC3518,G,12:34:31.29,+09:37:24.4,Vir -IC3519,G,12:34:38.40,+15:36:09.7,Com -IC3520,G,12:34:31.81,+13:30:13.2,Com -IC3521,G,12:34:39.51,+07:09:36.7,Vir -IC3522,G,12:34:45.58,+15:13:14.8,Com -IC3523,G,12:34:39.40,+14:01:00.4,Com -IC3524,*,12:34:43.10,+14:14:39.6,Com -IC3525,G,12:34:46.44,+10:10:36.1,Vir -IC3526,**,12:34:40.61,+25:41:02.9,Com -IC3527,**,12:34:42.29,+26:09:18.4,Com -IC3528,G,12:34:55.90,+15:33:56.2,Com -IC3529,*,12:34:49.76,+25:41:55.6,Com -IC3530,G,12:34:49.32,+17:48:51.4,Com -IC3531,G,12:34:56.54,+26:37:35.7,Com -IC3532,*,12:34:57.52,+25:52:50.3,Com -IC3533,G,12:35:01.28,+25:46:47.0,Com -IC3534,G,12:34:52.13,+14:58:41.4,Com -IC3535,*,12:35:10.90,+25:43:55.1,Com -IC3536,G,12:35:12.49,+26:32:00.3,Com -IC3537,*,12:35:22.46,+07:39:10.8,Vir -IC3538,*,12:35:15.56,+26:14:07.9,Com -IC3539,*,12:35:20.09,+23:58:59.3,Com -IC3540,G,12:35:27.23,+12:45:00.9,Vir -IC3541,*,12:35:21.72,+23:58:30.9,Com -IC3542,G,12:35:41.19,+11:40:01.8,Vir -IC3543,Dup,12:35:41.42,+26:17:09.0,Com -IC3544,**,12:35:47.40,+14:18:02.0,Com -IC3545,Dup,12:35:41.18,+26:31:23.2,Com -IC3546,Dup,12:35:41.69,+26:13:19.9,Com -IC3547,*,12:35:48.89,+26:19:44.8,Com -IC3548,G,12:35:56.63,+10:56:10.7,Vir -IC3549,*,12:35:50.85,+26:23:42.8,Com -IC3550,Dup,12:35:52.12,+27:55:55.5,Com -IC3551,HII,12:35:53.71,+27:57:51.0,Com -IC3552,HII,12:35:53.90,+27:59:38.0,Com -IC3553,*,12:35:55.93,+26:11:35.0,Com -IC3554,*,12:35:55.19,+27:55:38.4,Com -IC3555,HII,12:35:55.95,+27:59:20.0,Com -IC3556,G,12:35:58.46,+26:57:57.8,Com -IC3557,GPair,12:36:08.17,+16:38:27.1,Com -IC3557 NED01,G,12:36:08.14,+16:38:29.3,Com -IC3557 NED02,G,12:36:08.44,+16:38:26.1,Com -IC3558,G,12:36:02.80,+11:50:59.3,Vir -IC3559,G,12:36:03.36,+26:59:14.5,Com -IC3560,G,12:36:03.91,+27:04:41.5,Com -IC3561,G,12:36:04.79,+26:53:58.6,Com -IC3562,G,12:36:10.56,+09:55:21.4,Vir -IC3563,HII,12:36:07.22,+27:55:38.5,Com -IC3564,*Ass,12:36:08.09,+27:55:42.2,Com -IC3565,G,12:36:12.26,+26:45:22.0,Com -IC3566,Other,12:36:21.72,+11:09:53.6,Vir -IC3567,G,12:36:22.72,+13:36:10.3,Com -IC3568,PN,12:33:06.78,+82:33:50.1,Cam -IC3569,Dup,12:36:08.20,+19:19:22.5,Com -IC3570,*,12:36:18.28,+24:04:42.5,Com -IC3571,G,12:36:19.99,+26:05:01.6,Com -IC3572,**,12:36:27.93,+11:37:06.6,Vir -IC3573,G,12:36:27.22,+11:45:32.8,Vir -IC3574,G,12:36:27.83,+12:24:18.6,Vir -IC3575,G,12:36:32.38,+13:44:54.3,Com -IC3576,G,12:36:37.68,+06:37:15.2,Vir -IC3577,Other,12:36:36.27,+11:53:49.7,Vir -IC3578,G,12:36:39.41,+11:06:06.7,Vir -IC3579,*,12:36:32.74,+26:06:14.9,Com -IC3580,G,12:36:29.23,+18:18:01.8,Com -IC3581,G,12:36:38.11,+24:25:44.5,Com -IC3582,G,12:36:36.91,+26:14:04.8,Com -IC3583,G,12:36:43.49,+13:15:33.6,Vir -IC3584,*,12:36:45.10,+12:13:59.0,Vir -IC3585,G,12:36:39.90,+26:49:47.8,Com -IC3586,G,12:36:54.85,+12:31:12.3,Vir -IC3587,G,12:36:48.33,+27:32:54.7,Com -IC3588,Dup,12:36:56.38,+14:13:02.5,Com -IC3589,*,12:37:01.20,+06:56:13.2,Vir -IC3590,G,12:36:50.71,+27:16:41.3,Com -IC3591,G,12:37:03.04,+06:55:35.9,Vir -IC3592,Dup,12:36:53.44,+27:51:43.3,Com -IC3593,Dup,12:36:53.93,+27:44:56.8,Com -IC3594,*,12:36:56.39,+26:06:55.8,Com -IC3595,G,12:37:06.40,+23:47:12.8,Com -IC3596,Other,12:37:18.90,+26:31:15.3,Com -IC3597,G,12:37:24.64,+23:51:50.6,Com -IC3598,G,12:37:21.08,+28:12:29.4,Com -IC3599,G,12:37:41.20,+26:42:27.6,Com -IC3600,G,12:37:41.16,+27:07:46.4,Com -IC3601,G,12:37:53.64,+15:13:28.8,Com -IC3602,G,12:38:18.30,+10:04:22.2,Vir -IC3603,G,12:38:16.15,+15:34:11.3,Com -IC3604,G,12:38:20.72,+11:43:50.6,Vir -IC3605,G,12:38:20.95,+19:32:28.8,Com -IC3606,G,12:38:25.09,+12:36:38.2,Vir -IC3607,G,12:38:32.17,+10:22:35.5,Vir -IC3608,G,12:38:37.33,+10:28:33.2,Vir -IC3609,G,12:38:34.72,+14:21:08.7,Com -IC3610,G,12:38:47.14,+26:52:22.3,Com -IC3611,G,12:39:04.14,+13:21:48.7,Com -IC3612,G,12:39:04.70,+14:43:52.1,Com -IC3613,G,12:39:04.79,+13:45:32.4,Com -IC3614,G,12:39:01.08,+26:18:10.2,Com -IC3615,G,12:39:01.61,+18:12:02.4,Com -IC3616,Dup,12:39:04.70,+14:43:52.1,Com -IC3617,G,12:39:25.00,+07:57:57.0,Vir -IC3618,G,12:39:17.13,+26:40:40.0,Com -IC3619,**,12:39:18.78,+24:08:32.7,Com -IC3620,G,12:39:18.02,+27:54:31.1,Com -IC3621,G,12:39:33.58,+15:30:09.4,Com -IC3622,G,12:39:32.48,+15:25:55.4,Com -IC3623,G,12:39:27.62,+27:06:08.4,Com -IC3624,G,12:39:34.48,+11:58:55.9,Vir -IC3625,G,12:39:33.17,+10:58:03.6,Vir -IC3626,G,12:39:31.64,+25:40:38.6,Com -IC3627,G,12:39:32.00,+27:29:50.2,Com -IC3628,*,12:39:38.81,+26:14:20.0,Com -IC3629,G,12:39:46.66,+13:31:59.9,Com -IC3630,*,12:39:46.60,+25:25:57.1,Com -IC3631,G,12:39:48.01,+12:58:26.3,Vir -IC3632,G,12:40:00.00,+26:40:56.0,Com -IC3633,G,12:40:11.25,+09:53:46.1,Vir -IC3634,G,12:40:11.36,+09:50:52.0,Vir -IC3635,G,12:40:13.38,+12:52:29.2,Vir -IC3636,**,12:40:15.54,+22:04:28.4,Com -IC3637,G,12:40:19.57,+14:42:54.0,Com -IC3638,G,12:40:16.60,+10:31:06.7,Vir -IC3639,G,12:40:52.85,-36:45:21.1,Cen -IC3640,G,12:40:25.20,+26:31:27.0,Com -IC3641,G,12:40:26.88,+26:31:17.7,Com -IC3642,G,12:40:25.73,+26:43:52.4,Com -IC3643,G,12:40:40.87,+12:24:23.7,Vir -IC3644,G,12:40:36.23,+26:30:16.6,Com -IC3645,*,12:40:37.61,+26:32:28.9,Com -IC3646,G,12:40:38.50,+26:31:34.3,Com -IC3647,G,12:40:53.11,+10:28:31.3,Vir -IC3648,*,12:40:52.19,+12:59:06.0,Vir -IC3649,G,12:40:49.69,+21:06:17.1,Com -IC3650,**,12:40:48.51,+26:28:21.8,Com -IC3651,G,12:40:52.91,+26:43:41.3,Com -IC3652,G,12:40:58.56,+11:11:04.2,Vir -IC3653,G,12:41:15.73,+11:23:14.1,Vir -IC3654,G,12:41:12.50,+22:35:22.0,Com -IC3655,G,12:41:14.46,+20:39:58.3,Com -IC3656,G,12:41:13.93,+22:35:41.9,Com -IC3657,G,12:41:19.02,+21:40:21.0,Com -IC3658,G,12:41:20.63,+14:42:02.0,Com -IC3659,G,12:41:27.62,+22:55:50.9,Com -IC3660,*,12:41:36.84,+21:05:36.1,Com -IC3661,G,12:41:35.73,+22:29:41.5,Com -IC3662,G,12:41:36.27,+23:25:30.6,Com -IC3663,G,12:41:39.41,+12:14:50.6,Vir -IC3664,*,12:41:41.55,+19:56:38.9,Com -IC3665,G,12:41:46.70,+11:29:17.7,Vir -IC3666,*,12:41:53.37,+07:50:42.1,Vir -IC3667,Dup,12:41:32.85,+41:09:02.8,CVn -IC3668,Other,12:41:32.89,+41:07:27.0,CVn -IC3669,Other,12:41:35.91,+41:08:11.7,CVn -IC3670,G,12:41:55.02,+11:46:27.0,Vir -IC3671,G,12:41:51.38,+23:30:38.4,Com -IC3672,Dup,12:42:08.66,+11:45:15.4,Vir -IC3673,*,12:42:04.32,+21:08:18.0,Com -IC3674,**,12:42:05.12,+22:30:38.8,Com -IC3675,Dup,12:41:52.72,+41:16:26.3,CVn -IC3676,*,12:42:12.28,+13:33:35.4,Com -IC3677,G,12:42:11.84,+20:53:05.5,Com -IC3678,G,12:42:12.58,+20:52:49.7,Com -IC3679,**,12:42:11.23,+22:49:05.8,Com -IC3680,*,12:42:00.91,+39:06:15.3,CVn -IC3681,*,12:42:01.75,+39:05:00.3,CVn -IC3682,*,12:42:19.48,+20:51:52.0,Com -IC3683,G,12:42:20.64,+20:52:16.7,Com -IC3684,G,12:42:26.51,+11:44:25.1,Vir -IC3685,*,12:42:32.24,+06:52:15.3,Vir -IC3686,G,12:42:36.00,+10:33:55.1,Vir -IC3687,G,12:42:15.10,+38:30:12.0,CVn -IC3688,Dup,12:42:37.38,+14:21:25.9,Com -IC3689,G,12:42:36.96,+20:51:01.8,Com -IC3690,G,12:42:49.19,+10:21:26.9,Vir -IC3691,G,12:42:49.53,+22:46:20.0,Com -IC3692,G,12:42:54.00,+20:59:23.1,Com -IC3693,G,12:42:58.02,+10:40:54.5,Vir -IC3694,G,12:43:07.28,+11:12:43.2,Vir -IC3695,*,12:43:06.80,+22:44:31.0,Com -IC3696,G,12:43:09.64,+19:55:40.9,Com -IC3697,G,12:42:58.85,+39:50:44.4,CVn -IC3698,G,12:43:17.30,+11:12:41.5,Vir -IC3699,**,12:43:17.13,+19:00:00.7,Com -IC3700,*,12:43:20.00,+19:15:57.4,Com -IC3701,G,12:43:30.91,+11:02:49.7,Vir -IC3702,G,12:43:28.37,+10:52:25.9,Vir -IC3703,*,12:43:22.00,+37:58:28.3,CVn -IC3704,G,12:43:45.61,+10:46:12.3,Vir -IC3705,GPair,12:43:41.51,+19:19:31.0,Com -IC3705 NED01,G,12:43:41.52,+19:19:37.3,Com -IC3705 NED02,G,12:43:41.52,+19:19:23.3,Com -IC3706,**,12:43:47.99,+09:13:52.1,Vir -IC3707,*,12:43:28.68,+37:58:57.7,CVn -IC3708,Other,12:43:52.55,+13:08:15.1,Vir -IC3709,G,12:44:04.03,+09:03:48.0,Vir -IC3710,G,12:44:09.45,+12:06:59.5,Vir -IC3711,G,12:44:09.37,+11:10:35.8,Vir -IC3712,Other,12:44:16.65,+10:22:28.4,Vir -IC3713,G,12:44:03.17,+41:10:08.1,CVn -IC3714,G,12:44:23.08,+10:11:19.8,Vir -IC3715,G,12:44:21.41,+20:01:27.6,Com -IC3716,Other,12:44:45.14,+08:06:07.8,Vir -IC3717,G,12:44:22.96,+39:31:20.3,CVn -IC3718,G,12:44:45.98,+12:21:05.2,Vir -IC3719,G,12:44:47.50,+08:06:24.9,Vir -IC3720,G,12:44:47.48,+12:03:51.5,Vir -IC3721,G,12:44:53.12,+18:45:18.9,Com -IC3722,Other,12:44:50.69,+11:46:42.9,Vir -IC3723,G,12:44:30.55,+40:44:12.8,CVn -IC3724,G,12:44:53.76,+10:16:56.5,Vir -IC3725,Dup,12:44:53.12,+18:45:18.9,Com -IC3726,G,12:44:42.59,+40:40:44.4,CVn -IC3727,G,12:45:05.66,+10:54:03.2,Vir -IC3728,G,12:45:03.13,+20:58:26.7,Com -IC3729,G,12:44:53.12,+39:21:04.3,CVn -IC3730,G,12:45:06.56,+21:10:10.5,Com -IC3731,G,12:45:05.26,+12:26:46.9,Vir -IC3732,G,12:45:11.96,+10:19:28.0,Vir -IC3733,*,12:45:16.72,+06:57:24.8,Vir -IC3734,G,12:45:09.28,+23:02:20.6,Com -IC3735,G,12:45:20.42,+13:41:33.6,Com -IC3736,G,12:45:18.96,+21:32:08.7,Com -IC3737,**,12:45:19.87,+21:57:29.4,Com -IC3738,G,12:45:25.74,+19:13:51.1,Com -IC3739,Other,12:45:32.27,+12:59:50.5,Vir -IC3740,G,12:45:30.62,+20:48:57.4,Com -IC3741,**,12:45:33.28,+19:12:14.9,Com -IC3742,G,12:45:32.02,+13:19:57.3,Com -IC3743,Other,12:45:41.30,+11:06:07.6,Vir -IC3744,G,12:45:41.48,+19:30:01.2,Com -IC3745,G,12:45:44.86,+19:10:38.1,Com -IC3746,G,12:45:31.91,+37:49:24.8,CVn -IC3747,*,12:45:34.42,+37:58:06.7,CVn -IC3748,*,12:45:50.92,+19:25:49.2,Com -IC3749,*,12:45:51.38,+19:32:08.1,Com -IC3750,*,12:45:57.06,+19:06:13.9,Com -IC3751,G,12:45:45.10,+37:49:23.0,CVn -IC3752,**,12:46:04.09,+19:00:41.2,Com -IC3753,*,12:46:04.38,+19:07:16.2,Com -IC3754,G,12:46:15.54,+08:20:54.4,Vir -IC3755,**,12:46:09.41,+19:09:26.1,Com -IC3756,G,12:46:10.10,+11:54:52.6,Vir -IC3757,Other,12:45:59.78,+38:30:55.1,CVn -IC3758,G,12:45:59.67,+40:46:29.5,CVn -IC3759,G,12:46:17.77,+20:46:58.8,Com -IC3760,G,12:46:18.26,+11:52:24.9,Vir -IC3761,G,12:46:27.33,+20:17:20.9,Com -IC3762,G,12:46:37.60,+22:14:46.9,Com -IC3763,G,12:46:46.03,+21:59:06.4,Com -IC3764,Dup,12:46:56.80,+09:51:25.6,Vir -IC3765,**,12:46:35.15,+38:34:26.0,CVn -IC3766,G,12:46:53.52,+19:06:38.1,Com -IC3767,G,12:46:55.48,+10:10:56.7,Vir -IC3768,*,12:46:40.71,+40:35:51.6,CVn -IC3769,*,12:46:48.07,+40:28:12.7,CVn -IC3770,*,12:47:15.70,+09:11:56.8,Vir -IC3771,G,12:46:52.69,+39:10:23.0,CVn -IC3772,G,12:46:56.09,+36:31:51.4,CVn -IC3773,G,12:47:15.30,+10:12:12.9,Vir -IC3774,G,12:47:01.09,+36:17:17.9,CVn -IC3775,G,12:47:16.10,+11:45:36.8,Vir -IC3776,G,12:47:12.21,+22:29:04.6,Com -IC3777,*,12:47:25.35,+09:08:36.5,Vir -IC3778,G,12:47:01.97,+40:35:47.1,CVn -IC3779,G,12:47:20.64,+12:09:59.1,Vir -IC3780,*,12:47:08.08,+40:14:09.7,CVn -IC3781,**,12:47:24.57,+22:34:11.2,Com -IC3782,*,12:47:15.67,+40:22:03.9,CVn -IC3783,G,12:47:27.84,+40:33:59.7,CVn -IC3784,G,12:47:50.70,+19:23:02.6,Com -IC3785,G,12:47:52.17,+19:16:30.8,Com -IC3786,G,12:47:36.90,+39:02:45.7,CVn -IC3787,**,12:47:41.85,+40:37:25.6,CVn -IC3788,G,12:48:07.26,+18:52:07.9,Com -IC3789,G,12:48:07.10,+20:11:38.5,Com -IC3790,*,12:48:14.65,+11:06:27.3,Vir -IC3791,Dup,12:47:32.11,+54:22:29.4,UMa -IC3792,**,12:48:14.16,+11:05:10.8,Vir -IC3793,G,12:48:11.89,+19:09:06.1,Com -IC3794,*,12:48:21.51,+19:10:13.1,Com -IC3795,G,12:48:05.11,+40:43:10.7,CVn -IC3796,*,12:48:27.00,+20:02:15.7,Com -IC3797,Other,12:48:34.91,+11:35:52.1,Vir -IC3798,*,12:48:42.92,+09:14:27.7,Vir -IC3799,G,12:48:59.64,-14:23:57.2,Crv -IC3800,GPair,12:48:26.43,+36:34:33.1,CVn -IC3800 NED01,G,12:48:26.42,+36:34:33.1,CVn -IC3800 NED02,G,12:48:27.25,+36:34:31.3,CVn -IC3801,Other,12:49:00.65,+10:57:21.5,Vir -IC3802,*,12:48:42.61,+38:14:47.2,CVn -IC3803,*,12:49:04.40,+10:37:54.5,Vir -IC3804,Dup,12:48:45.87,+35:19:57.7,CVn -IC3805,**,12:48:42.63,+38:15:11.0,CVn -IC3806,G,12:48:55.36,+14:54:28.4,Com -IC3807,Other,12:49:29.80,-04:24:08.2,Vir -IC3808,G,12:48:58.76,+40:35:45.3,CVn -IC3809,G,12:49:04.51,+36:29:20.7,CVn -IC3810,*,12:49:03.18,+40:38:47.9,CVn -IC3811,*,12:49:25.57,+21:27:43.7,Com -IC3812,G,12:49:53.80,-06:43:02.8,Vir -IC3813,G,12:50:02.32,-25:55:14.5,Hya -IC3814,G,12:49:32.41,+20:03:01.7,Com -IC3815,G,12:49:38.53,+19:16:30.1,Com -IC3816,G,12:49:28.47,+37:13:49.0,CVn -IC3817,G,12:49:43.47,+22:49:52.9,Com -IC3818,G,12:49:46.73,+21:45:08.0,Com -IC3819,G,12:50:16.37,-14:22:50.7,Crv -IC3820,G,12:49:38.95,+37:07:01.8,CVn -IC3821,**,12:49:57.55,+20:58:08.5,Com -IC3822,G,12:50:22.73,-14:19:18.7,Crv -IC3823,*,12:49:43.98,+40:53:00.4,CVn -IC3824,G,12:50:30.55,-14:25:33.3,Crv -IC3825,G,12:50:37.05,-14:28:58.1,Crv -IC3826,G,12:50:39.90,-09:01:51.9,Vir -IC3827,G,12:50:52.12,-14:29:30.8,Crv -IC3828,G,12:50:20.70,+37:56:56.2,CVn -IC3829,G,12:52:13.30,-29:50:26.4,Hya -IC3830,*,12:50:51.37,+19:50:12.1,Com -IC3831,G,12:51:18.59,-14:34:24.9,Crv -IC3832,G,12:50:49.20,+39:48:37.7,CVn -IC3833,Dup,12:51:32.39,-13:19:48.1,Crv -IC3834,G,12:51:32.40,-14:13:17.0,Crv -IC3835,G,12:50:55.84,+40:11:12.3,CVn -IC3836,G,12:51:03.72,+40:11:03.2,CVn -IC3837,G,12:51:33.01,+19:43:23.0,Com -IC3838,Dup,12:50:52.12,-14:29:30.8,Crv -IC3839,*,12:51:46.26,+20:25:15.5,Com -IC3840,G,12:51:46.10,+21:44:06.7,Com -IC3841,**,12:51:50.50,+22:20:40.2,Com -IC3842,G,12:51:35.88,+40:22:16.9,CVn -IC3843,G,12:51:39.19,+39:00:04.7,CVn -IC3844,G,12:52:06.50,+39:49:05.7,CVn -IC3845,*,12:52:08.66,+38:37:08.4,CVn -IC3846,Other,12:52:39.22,+13:38:50.0,Com -IC3847,G,12:52:33.74,+22:03:53.0,Com -IC3848,GPair,12:52:40.19,+21:24:58.0,Com -IC3848 NED01,G,12:52:40.00,+21:25:00.0,Com -IC3848 NED02,G,12:52:40.59,+21:24:52.7,Com -IC3849,Other,12:52:36.83,+40:46:20.0,CVn -IC3850,G,12:52:39.52,+40:06:11.0,CVn -IC3851,*,12:53:04.73,+21:54:33.0,Com -IC3852,G,12:53:03.30,+35:46:24.4,CVn -IC3853,G,12:53:10.30,+38:49:45.4,CVn -IC3854,G,12:53:14.54,+40:50:53.4,CVn -IC3855,G,12:53:22.69,+36:47:09.6,CVn -IC3856,G,12:53:45.92,+20:05:31.5,Com -IC3857,G,12:53:56.06,+19:36:25.7,Com -IC3858,*,12:53:55.54,+20:47:20.5,Com -IC3859,G,12:54:19.86,-09:07:03.8,Vir -IC3860,G,12:54:06.71,+19:18:02.6,Com -IC3861,G,12:53:50.83,+38:16:54.5,CVn -IC3862,G,12:53:52.96,+36:05:13.0,CVn -IC3863,G,12:53:53.84,+38:28:49.6,CVn -IC3864,G,12:54:12.34,+18:57:04.6,Com -IC3865,G,12:54:14.15,+18:52:08.2,Com -IC3866,GPair,12:54:15.22,+22:21:42.6,Com -IC3866 NED01,G,12:54:14.97,+22:21:34.7,Com -IC3866 NED02,G,12:54:15.50,+22:21:51.4,Com -IC3867,G,12:54:19.58,+18:56:30.5,Com -IC3868,G,12:54:21.00,+18:59:25.2,Com -IC3869,G,12:54:21.27,+18:58:17.1,Com -IC3870,G,12:54:21.58,+22:22:54.1,Com -IC3871,G,12:54:25.67,+18:55:44.9,Com -IC3872,G,12:54:30.56,+18:57:47.4,Com -IC3873,G,12:54:31.60,+18:52:57.6,Com -IC3874,G,12:54:34.42,+18:57:25.2,Com -IC3875,G,12:54:37.17,+22:02:11.2,Com -IC3876,G,12:54:48.33,+19:00:55.2,Com -IC3877,G,12:54:48.68,+19:10:41.8,Com -IC3878,*,12:54:29.56,+40:04:11.0,CVn -IC3879,G,12:54:31.87,+38:37:42.8,CVn -IC3880,G,12:54:47.99,+22:30:08.0,Com -IC3881,G,12:54:53.95,+19:07:05.0,Com -IC3882,G,12:54:53.71,+22:34:32.1,Com -IC3883,G,12:55:13.52,-08:07:11.1,Vir -IC3884,G,12:54:57.73,+19:40:52.6,Com -IC3885,G,12:54:42.73,+37:09:17.0,CVn -IC3886,G,12:55:00.30,+19:00:42.1,Com -IC3887,**,12:54:43.77,+40:18:16.7,CVn -IC3888,G,12:54:46.37,+39:34:16.6,CVn -IC3889,*,12:54:50.90,+36:01:00.6,CVn -IC3890,G,12:54:50.26,+37:11:07.1,CVn -IC3891,G,12:54:58.25,+36:03:09.8,CVn -IC3892,G,12:55:06.04,+39:13:19.0,CVn -IC3893,G,12:55:07.45,+38:37:25.9,CVn -IC3894,**,12:55:27.61,+19:04:08.8,Com -IC3895,G,12:55:09.23,+39:12:11.7,CVn -IC3896,G,12:56:43.23,-50:20:48.7,Cen -IC3897,G,12:55:19.10,+39:40:21.4,CVn -IC3898,G,12:55:23.84,+37:34:58.4,CVn -IC3899,G,12:55:40.66,+20:38:11.2,Com -IC3900,G,12:55:41.30,+27:15:02.7,Com -IC3901,*,12:55:50.50,+21:56:20.5,Com -IC3902,*,12:55:38.58,+35:59:45.0,CVn -IC3903,*,12:55:38.74,+40:23:58.8,CVn -IC3904,G,12:55:45.60,+36:17:36.3,CVn -IC3905,GTrpl,12:56:08.59,+19:51:07.5,Com -IC3905 NED01,G,12:56:07.99,+19:51:21.2,Com -IC3905 NED02,G,12:56:08.58,+19:51:11.6,Com -IC3905 NED03,G,12:56:09.27,+19:50:48.2,Com -IC3906,*,12:55:51.06,+40:27:49.9,CVn -IC3907,G,12:56:18.18,+18:47:02.5,Com -IC3908,G,12:56:40.62,-07:33:46.0,Vir -IC3909,G,12:56:02.82,+40:23:07.6,CVn -IC3910,**,12:56:04.53,+39:43:11.6,CVn -IC3911,GPair,12:56:09.43,+35:38:17.7,CVn -IC3911 NED01,G,12:56:09.22,+35:38:12.3,CVn -IC3911 NED02,G,12:56:09.60,+35:38:23.0,CVn -IC3912,*,12:56:07.54,+39:54:38.4,CVn -IC3913,G,12:56:28.57,+27:17:28.6,Com -IC3914,G,12:56:22.78,+36:21:39.3,CVn -IC3915,*,12:56:39.02,+20:17:30.1,Com -IC3916,G,12:56:31.17,+38:36:48.4,CVn -IC3917,*,12:56:51.63,+22:00:21.8,Com -IC3918,G,12:56:53.54,+22:22:25.0,Com -IC3919,G,12:56:48.71,+38:35:19.5,CVn -IC3920,G,12:56:50.05,+39:57:31.2,CVn -IC3921,G,12:56:56.65,+38:38:22.2,CVn -IC3922,G,12:56:57.50,+38:28:41.8,CVn -IC3923,**,12:57:01.13,+37:57:20.6,CVn -IC3924,G,12:57:24.88,+18:46:53.5,Com -IC3925,**,12:57:15.12,+36:25:19.2,CVn -IC3926,*,12:57:30.41,+22:48:43.0,Com -IC3927,G,12:58:10.39,-22:52:33.7,Hya -IC3928,G,12:57:18.29,+40:26:29.8,CVn -IC3929,G,12:57:41.09,+20:23:48.8,Com -IC3930,G,12:57:22.39,+38:45:53.7,CVn -IC3931,G,12:57:57.20,+19:37:03.5,Com -IC3932,*,12:58:05.30,+19:35:00.4,Com -IC3933,*,12:57:57.20,+36:38:42.2,CVn -IC3934,G,12:58:17.56,+18:49:31.5,Com -IC3935,Dup,12:58:12.68,+26:23:48.8,Com -IC3936,Other,12:58:19.93,+19:03:29.8,Com -IC3937,G,12:58:24.74,+18:49:07.2,Com -IC3938,*,12:58:25.36,+18:45:09.5,Com -IC3939,*,12:58:28.02,+18:45:06.7,Com -IC3940,G,12:58:16.47,+35:50:20.2,CVn -IC3941,G,12:58:13.91,+39:46:22.2,CVn -IC3942,*,12:58:19.89,+36:06:31.5,CVn -IC3943,G,12:58:36.35,+28:06:49.5,Com -IC3944,G,12:58:44.75,+23:46:51.8,Com -IC3945,G,12:58:29.63,+39:56:09.3,CVn -IC3946,G,12:58:48.72,+27:48:37.4,Com -IC3947,G,12:58:52.10,+27:47:06.2,Com -IC3948,G,12:58:58.15,+24:03:40.9,Com -IC3949,G,12:58:56.01,+27:50:00.1,Com -IC3950,G,12:59:06.12,+18:44:08.2,Com -IC3951,G,12:59:10.20,+18:45:51.4,Com -IC3952,G,12:58:52.15,+38:52:10.2,CVn -IC3953,G,12:59:09.12,+23:05:11.8,Com -IC3954,G,12:59:12.65,+19:16:22.6,Com -IC3955,G,12:59:06.03,+27:59:48.1,Com -IC3956,G,12:58:56.38,+37:23:53.4,CVn -IC3957,G,12:59:07.49,+27:46:04.0,Com -IC3958,G,12:59:11.41,+24:01:20.2,Com -IC3959,G,12:59:08.20,+27:47:02.9,Com -IC3960,G,12:59:07.96,+27:51:18.0,Com -IC3961,Dup,12:59:02.34,+34:51:34.0,CVn -IC3962,*,12:59:14.98,+23:40:04.8,Com -IC3963,G,12:59:13.50,+27:46:28.6,Com -IC3964,*,12:59:13.73,+27:51:03.4,Com -IC3965,G,12:59:22.68,+18:50:34.6,Com -IC3966,G,12:59:13.12,+35:51:19.2,CVn -IC3967,G,12:59:12.90,+36:07:45.4,CVn -IC3968,G,12:59:25.49,+27:58:23.5,Com -IC3969,GTrpl,12:59:32.91,+19:39:07.2,Com -IC3969 NED01,G,12:59:32.71,+19:39:11.0,Com -IC3969 NED02,G,12:59:32.65,+19:38:51.2,Com -IC3969 NED03,G,12:59:33.73,+19:39:17.1,Com -IC3970,*,12:59:11.43,+40:24:06.9,CVn -IC3971,G,12:59:31.79,+22:50:41.3,Com -IC3972,*,12:59:17.15,+37:16:44.9,CVn -IC3973,G,12:59:30.82,+27:53:03.2,Com -IC3974,Dup,13:05:20.20,-35:20:14.7,Cen -IC3975,G,12:59:15.69,+38:52:57.9,CVn -IC3976,G,12:59:29.40,+27:51:00.5,Com -IC3977,*,12:59:19.66,+36:47:53.1,CVn -IC3978,G,12:59:37.45,+19:37:20.0,Com -IC3979,*,12:59:20.75,+36:19:27.6,CVn -IC3980,G,12:59:18.60,+39:09:04.5,CVn -IC3981,*,12:59:21.45,+37:13:40.1,CVn -IC3982,*,12:59:18.59,+40:04:49.9,CVn -IC3983,**,12:59:20.51,+39:14:47.9,CVn -IC3984,G,12:59:40.26,+19:37:23.1,Com -IC3985,G,12:59:43.15,+19:35:28.0,Com -IC3986,G,13:01:32.14,-32:17:27.8,Cen -IC3987,G,12:59:25.04,+38:44:00.4,CVn -IC3988,*,12:59:26.82,+37:14:41.9,CVn -IC3989,*,12:59:28.74,+36:45:23.7,CVn -IC3990,G,12:59:39.11,+28:53:43.9,Com -IC3991,G,12:59:39.79,+28:55:35.7,Com -IC3992,*,12:59:33.29,+36:46:18.6,CVn -IC3993,G,12:59:30.47,+40:36:06.4,CVn -IC3994,G,12:59:50.84,+22:43:00.3,Com -IC3995,*,12:59:33.93,+39:02:23.2,CVn -IC3996,**,12:59:31.50,+40:28:03.2,CVn -IC3997,G,12:59:36.93,+36:41:44.4,CVn -IC3998,G,12:59:46.78,+27:58:25.9,Com -IC3999,Dup,12:59:30.82,-14:07:56.4,Vir -IC4000,G,12:59:36.62,+39:35:15.9,CVn -IC4001,G,12:59:37.93,+38:52:12.9,CVn -IC4002,G,12:59:40.61,+36:45:48.5,CVn -IC4003,G,12:59:39.33,+38:48:56.1,CVn -IC4004,G,12:59:42.77,+38:48:40.7,CVn -IC4005,**,13:00:02.54,+22:38:23.7,Com -IC4006,*,12:59:48.72,+37:00:39.9,CVn -IC4007,G,13:00:06.99,+19:57:52.8,Com -IC4008,**,13:00:05.74,+22:21:01.8,Com -IC4009,*,12:59:52.80,+36:39:38.7,CVn -IC4010,G,12:59:54.04,+37:51:32.8,CVn -IC4011,G,13:00:06.39,+28:00:14.9,Com -IC4012,G,13:00:07.99,+28:04:42.8,Com -IC4013,*,12:59:57.83,+37:11:57.8,CVn -IC4014,G,13:00:13.90,+22:29:58.6,Com -IC4015,Dup,12:59:59.61,+37:11:36.2,CVn -IC4016,Dup,12:59:59.84,+37:11:17.3,CVn -IC4017,G,13:00:15.92,+22:33:20.5,Com -IC4018,G,12:59:57.44,+40:29:19.4,CVn -IC4019,**,13:00:17.43,+23:43:15.0,Com -IC4020,G,13:00:03.40,+38:36:35.6,CVn -IC4021,G,13:00:14.74,+28:02:28.7,Com -IC4022,*,13:00:04.93,+38:28:44.1,CVn -IC4023,G,13:00:26.54,+19:05:50.4,Com -IC4024,*,13:00:03.97,+40:30:32.9,CVn -IC4025,G,13:00:28.36,+19:06:18.1,Com -IC4026,G,13:00:22.14,+28:02:49.2,Com -IC4027,G,13:00:13.62,+37:08:29.1,CVn -IC4028,G,13:00:16.13,+36:15:14.8,CVn -IC4029,G,13:00:14.20,+38:45:35.0,CVn -IC4030,G,13:00:27.97,+27:57:21.5,Com -IC4031,G,13:00:15.48,+39:08:41.8,CVn -IC4032,G,13:00:25.65,+28:52:04.1,Com -IC4033,G,13:00:28.39,+27:58:20.7,Com -IC4034,G,13:00:19.59,+37:02:46.3,CVn -IC4035,GPair,13:00:17.34,+40:18:05.2,CVn -IC4035 NED01,G,13:00:16.69,+40:17:58.7,CVn -IC4035 NED02,G,13:00:18.00,+40:18:08.5,CVn -IC4036,*,13:00:20.75,+36:54:32.6,CVn -IC4037,G,13:00:19.42,+39:00:09.5,CVn -IC4038,G,13:00:21.77,+37:02:21.7,CVn -IC4039,G,13:00:39.38,+21:41:30.2,Com -IC4040,G,13:00:37.93,+28:03:26.5,Com -IC4041,G,13:00:40.85,+27:59:47.8,Com -IC4042,G,13:00:42.77,+27:58:17.0,Com -IC4043,G,13:00:34.68,+37:04:18.9,CVn -IC4044,G,13:00:47.41,+27:55:19.8,Com -IC4045,G,13:00:48.64,+28:05:26.8,Com -IC4046,*,13:00:39.43,+36:41:09.3,CVn -IC4047,G,13:00:57.69,+19:41:13.2,Com -IC4048,G,13:00:38.15,+39:49:51.5,CVn -IC4049,G,13:00:42.66,+36:20:44.0,CVn -IC4050,**,13:00:43.82,+36:44:23.5,CVn -IC4051,G,13:00:51.56,+28:02:34.1,Com -IC4052,**,13:00:41.92,+39:40:03.5,CVn -IC4053,*,13:01:01.31,+22:55:29.0,Com -IC4054,*,13:01:01.36,+22:54:18.1,Com -IC4055,Other,13:01:02.63,+22:54:30.8,Com -IC4056,G,13:00:44.29,+39:45:14.9,CVn -IC4057,*,13:01:05.26,+23:09:27.7,Com -IC4058,G,13:01:09.34,+19:29:39.0,Com -IC4059,G,13:01:15.47,+19:16:24.8,Com -IC4060,G,13:00:52.56,+40:35:04.4,CVn -IC4061,GPair,13:00:57.64,+39:34:57.9,CVn -IC4061 NED01,G,13:00:57.39,+39:34:59.2,CVn -IC4061 NED02,G,13:00:58.10,+39:35:02.2,CVn -IC4062,G,13:00:58.64,+39:51:32.2,CVn -IC4063,G,13:01:06.65,+39:14:42.7,CVn -IC4064,G,13:01:06.73,+39:50:29.3,CVn -IC4065,G,13:01:10.99,+39:44:40.1,CVn -IC4066,*,13:01:40.16,+19:16:21.4,Com -IC4067,G,13:01:20.10,+39:56:25.3,CVn -IC4068,G,13:01:20.18,+39:53:56.6,CVn -IC4069,G,13:01:24.78,+36:06:45.8,CVn -IC4070,G,13:01:43.29,+19:18:07.0,Com -IC4071,G,13:02:04.09,-07:36:11.5,Vir -IC4072,*,13:01:25.84,+37:21:13.4,CVn -IC4073,G,13:01:25.71,+39:54:51.4,CVn -IC4074,G,13:01:48.98,+19:00:31.9,Com -IC4075,G,13:01:48.56,+19:57:54.0,Com -IC4076,G,13:01:48.59,+23:23:20.8,Com -IC4077,G,13:01:34.05,+37:23:10.4,CVn -IC4078,**,13:01:35.66,+36:35:35.6,CVn -IC4079,G,13:01:56.79,+19:14:55.7,Com -IC4080,GPair,13:01:57.80,+19:15:16.9,Com -IC4080 NED01,G,13:01:57.71,+19:15:11.7,Com -IC4080 NED02,G,13:01:58.25,+19:15:21.3,Com -IC4081,G,13:01:55.30,+22:46:15.7,Com -IC4082,G,13:01:39.09,+37:20:29.9,CVn -IC4083,G,13:01:38.91,+38:08:32.5,CVn -IC4084,**,13:01:41.03,+36:57:56.4,CVn -IC4085,G,13:01:38.19,+39:42:09.7,CVn -IC4086,G,13:01:42.84,+36:38:54.3,CVn -IC4087,G,13:02:00.41,+19:59:42.7,Com -IC4088,Dup,13:01:43.37,+29:02:40.8,Com -IC4089,G,13:02:01.85,+19:30:10.4,Com -IC4090,*,13:01:46.30,+36:50:16.6,CVn -IC4091,G,13:02:12.96,+19:53:34.8,Com -IC4092,*,13:02:13.63,+19:11:03.0,Com -IC4093,Other,13:02:03.99,+28:59:41.1,Com -IC4094,G,13:01:58.59,+37:47:42.1,CVn -IC4095,G,13:02:20.38,+19:05:58.9,Com -IC4096,G,13:02:16.96,+24:00:39.0,Com -IC4097,*,13:02:04.94,+36:36:19.6,CVn -IC4098,G,13:02:03.84,+37:58:51.2,CVn -IC4099,G,13:02:23.23,+24:01:45.0,Com -IC4100,G,13:02:04.91,+40:24:30.1,CVn -IC4101,*,13:02:13.75,+39:56:25.3,CVn -IC4102,G,13:02:17.99,+36:09:08.8,CVn -IC4103,G,13:02:19.05,+38:01:03.0,CVn -IC4104,G,13:02:18.12,+38:35:32.7,CVn -IC4105,G,13:02:19.00,+38:16:16.6,CVn -IC4106,G,13:02:38.44,+28:06:52.5,Com -IC4107,G,13:02:41.87,+21:59:50.6,Com -IC4108,G,13:02:31.53,+38:28:42.6,CVn -IC4109,*,13:02:57.97,+19:00:13.3,Com -IC4110,G,13:02:58.29,+19:13:37.1,Com -IC4111,G,13:02:56.57,+28:04:13.5,Com -IC4112,*,13:02:45.16,+37:12:43.8,CVn -IC4113,G,13:03:03.31,+20:28:24.2,Com -IC4114,G,13:02:41.83,+40:06:16.3,CVn -IC4115,G,13:02:48.73,+37:13:23.6,CVn -IC4116,*,13:03:10.12,+19:05:00.1,Com -IC4117,*,13:02:48.60,+40:31:29.6,CVn -IC4118,G,13:02:51.70,+38:17:34.6,CVn -IC4119,G,13:03:15.17,+19:13:58.0,Com -IC4120,*,13:03:01.55,+37:04:54.0,CVn -IC4121,*,13:03:21.52,+19:16:56.3,Com -IC4122,G,13:03:24.43,+20:11:48.9,Com -IC4123,G,13:03:05.82,+38:18:52.1,CVn -IC4124,G,13:03:31.57,+22:50:48.5,Com -IC4125,G,13:03:34.97,+18:48:12.2,Com -IC4126,*,13:03:36.45,+19:19:24.4,Com -IC4127,G,13:03:17.40,+38:02:48.9,CVn -IC4128,G,13:03:41.24,+20:12:59.9,Com -IC4129,G,13:03:43.97,+18:52:37.7,Com -IC4130,G,13:03:46.59,+19:16:17.5,Com -IC4131,G,13:03:25.58,+38:57:04.6,CVn -IC4132,*,13:03:33.84,+38:22:39.3,CVn -IC4133,G,13:03:50.80,+27:59:18.2,Com -IC4134,Dup,13:02:04.15,-11:22:42.3,Vir -IC4135,G,13:03:37.39,+40:14:56.3,CVn -IC4136,Dup,13:04:19.11,-07:38:58.0,Vir -IC4137,G,13:03:59.30,+22:44:23.9,Com -IC4138,G,13:04:02.11,+20:39:55.8,Com -IC4139,G,13:04:04.15,+19:17:41.9,Com -IC4140,G,13:04:06.04,+20:05:46.9,Com -IC4141,G,13:04:07.73,+19:12:38.3,Com -IC4142,*,13:03:47.19,+38:11:42.3,CVn -IC4143,*,13:03:45.50,+40:12:25.5,CVn -IC4144,G,13:03:50.07,+36:56:36.2,CVn -IC4145,G,13:03:49.77,+38:17:11.7,CVn -IC4146,G,13:04:10.38,+19:16:41.7,Com -IC4147,G,13:04:09.43,+20:15:03.2,Com -IC4148,G,13:04:10.69,+19:15:33.0,Com -IC4149,G,13:04:10.82,+22:17:22.8,Com -IC4150,*,13:04:13.04,+21:59:12.2,Com -IC4151,GTrpl,13:03:58.94,+36:51:28.4,CVn -IC4151 NED01,G,13:03:59.08,+36:51:28.9,CVn -IC4151 NED02,G,13:03:59.34,+36:51:30.4,CVn -IC4152,G,13:03:58.46,+38:11:55.0,CVn -IC4153,*,13:04:26.57,+19:02:41.9,Com -IC4154,G,13:04:28.27,+23:34:30.1,Com -IC4155,*,13:04:08.90,+40:00:55.6,CVn -IC4156,Dup,13:04:55.96,-07:56:51.7,Vir -IC4157,*,13:04:18.58,+38:39:48.9,CVn -IC4158,G,13:04:24.64,+36:28:47.6,CVn -IC4159,G,13:04:45.99,+22:14:31.7,Com -IC4160,G,13:04:48.06,+22:53:32.9,Com -IC4161,G,13:04:35.32,+39:58:38.9,CVn -IC4162,G,13:05:02.99,+20:33:17.3,Com -IC4163,G,13:05:08.01,+20:46:14.6,Com -IC4164,G,13:05:14.49,+20:32:49.6,Com -IC4165,G,13:04:56.96,+39:55:29.7,CVn -IC4166,G,13:05:18.59,+31:26:32.3,CVn -IC4167,G,13:05:30.75,+21:54:36.1,Com -IC4168,G,13:05:10.71,+40:02:58.9,CVn -IC4169,GPair,13:05:12.09,+38:46:20.9,CVn -IC4169 NED01,G,13:05:12.01,+38:46:14.5,CVn -IC4169 NED02,G,13:05:12.52,+38:46:24.8,CVn -IC4170,GPair,13:05:34.85,+21:08:06.1,Com -IC4170 NED01,G,13:05:34.99,+21:08:05.8,Com -IC4170 NED02,G,13:05:35.27,+21:07:46.9,Com -IC4171,G,13:05:18.81,+36:06:10.4,CVn -IC4172,G,13:06:33.36,+22:51:01.4,Com -IC4173,Dup,13:03:54.73,-11:30:18.2,Vir -IC4174,*,13:05:28.56,+36:23:47.1,CVn -IC4175,G,13:05:47.47,+20:22:28.9,Com -IC4176,Dup,13:03:56.74,-11:29:53.0,Vir -IC4177,G,13:06:28.95,-13:34:17.9,Vir -IC4178,G,13:05:41.50,+36:01:02.9,CVn -IC4179,*,13:05:45.90,+37:11:52.1,CVn -IC4180,G,13:06:56.49,-23:55:01.6,Hya -IC4181,G,13:06:06.42,+21:29:38.0,Com -IC4182,G,13:05:49.54,+37:36:17.6,CVn -IC4183,G,13:06:11.56,+21:30:15.7,Com -IC4184,G,13:05:51.77,+38:50:14.6,CVn -IC4185,GPair,13:06:12.64,+21:46:22.9,Com -IC4185 NED01,G,13:06:12.57,+21:46:09.3,Com -IC4185 NED02,G,13:06:12.77,+21:46:33.1,Com -IC4186,**,13:05:57.36,+36:59:09.8,CVn -IC4187,G,13:05:59.62,+36:17:54.5,CVn -IC4188,G,13:06:02.33,+36:19:41.6,CVn -IC4189,G,13:06:03.55,+35:58:49.2,CVn -IC4190,*,13:06:06.38,+37:36:36.1,CVn -IC4191,PN,13:08:47.23,-67:38:37.5,Mus -IC4192,Other,13:06:08.60,+37:36:20.0,CVn -IC4193,G,13:06:06.33,+39:25:25.0,CVn -IC4194,*,13:06:07.79,+38:52:24.9,CVn -IC4195,*,13:06:15.43,+37:02:23.2,CVn -IC4196,Dup,13:07:33.74,-24:00:30.8,Hya -IC4197,G,13:08:04.33,-23:47:48.7,Hya -IC4198,Dup,13:07:42.82,+24:48:38.1,Com -IC4199,*,13:07:32.67,+35:51:35.4,CVn -IC4200,G,13:09:34.76,-51:58:06.9,Cen -IC4201,G,13:07:51.22,+35:50:05.5,CVn -IC4202,G,13:08:31.58,+24:42:02.8,Com -IC4203,*,13:08:19.07,+40:25:40.4,CVn -IC4204,G,13:08:21.58,+39:27:40.2,CVn -IC4205,Dup,13:08:41.73,+52:46:27.4,UMa -IC4206,*,13:09:21.94,+39:01:20.1,CVn -IC4207,G,13:09:26.73,+37:49:22.6,CVn -IC4208,*,13:09:37.99,+37:15:20.1,CVn -IC4209,G,13:10:22.49,-07:10:14.5,Vir -IC4210,Dup,13:10:47.65,+29:42:35.6,Com -IC4211,*,13:10:56.48,+37:10:34.5,CVn -IC4212,G,13:12:02.99,-06:59:33.0,Vir -IC4213,G,13:12:11.22,+35:40:10.9,CVn -IC4214,G,13:17:42.69,-32:06:06.1,Cen -IC4215,G,13:16:16.83,+25:24:18.8,Com -IC4216,G,13:17:01.87,-10:46:12.0,Vir -IC4217,G,13:17:13.28,-13:09:14.7,Vir -IC4218,G,13:17:03.41,-02:15:40.6,Vir -IC4219,G,13:18:29.74,-31:37:51.2,Cen -IC4220,G,13:17:54.32,-13:36:19.7,Vir -IC4221,G,13:18:30.35,-14:36:32.0,Vir -IC4222,Dup,13:19:40.57,-27:25:44.3,Hya -IC4223,G,13:18:55.21,+07:47:43.8,Vir -IC4224,G,13:19:05.05,-02:30:55.1,Vir -IC4225,G,13:20:00.95,+31:58:53.0,CVn -IC4226,G,13:20:30.41,+32:00:14.1,CVn -IC4227,G,13:20:53.48,+32:11:26.8,CVn -IC4228,G,13:21:34.10,+25:30:58.5,Com -IC4229,G,13:22:26.13,-02:25:05.8,Vir -IC4230,G,13:21:59.20,+26:44:01.9,Com -IC4231,G,13:23:13.38,-26:18:01.4,Hya -IC4232,G,13:23:22.45,-26:06:34.2,Hya -IC4233,Dup,13:24:50.43,-30:18:27.1,Cen -IC4234,G,13:22:59.87,+27:06:59.1,Com -IC4235,G,13:23:52.97,-12:44:36.1,Vir -IC4236,Dup,13:23:27.45,+06:23:33.2,Vir -IC4237,G,13:24:32.76,-21:08:12.6,Vir -IC4238,G,13:23:59.95,+30:55:56.7,CVn -IC4239,G,13:24:25.43,+30:57:33.4,CVn -IC4240,G,13:24:27.56,+30:58:40.5,CVn -IC4241,G,13:24:46.55,+26:44:18.3,Com -IC4242,G,13:24:40.92,+31:01:35.4,CVn -IC4243,G,13:25:51.24,-27:37:36.8,Hya -IC4244,G,13:24:56.27,+26:27:48.7,Com -IC4245,G,13:25:59.05,-26:40:39.8,Hya -IC4246,G,13:26:00.25,-26:40:41.0,Hya -IC4247,G,13:26:44.43,-30:21:44.7,Cen -IC4248,G,13:26:47.22,-29:52:52.9,Hya -IC4249,G,13:27:06.56,-27:57:23.0,Hya -IC4250,G,13:26:08.94,+26:28:37.8,Com -IC4251,G,13:27:24.28,-29:26:39.7,Hya -IC4252,G,13:27:28.00,-27:19:29.2,Hya -IC4253,G,13:27:32.37,-27:52:19.7,Hya -IC4254,G,13:27:45.37,-27:13:21.1,Hya -IC4255,G,13:28:00.12,-27:21:15.5,Hya -IC4256,G,13:27:03.19,+30:58:36.6,CVn -IC4257,Other,13:27:20.33,+46:52:01.2,CVn -IC4258,G,13:27:53.26,+28:30:29.5,CVn -IC4259,G,13:29:28.15,-30:08:05.8,Hya -IC4260,G,13:29:40.42,-28:15:57.9,Hya -IC4261,G,13:29:47.66,-28:00:22.9,Hya -IC4262,G,13:30:23.10,-28:16:14.1,Hya -IC4263,G,13:28:33.19,+46:55:37.9,CVn -IC4264,G,13:30:17.72,-27:55:42.4,Hya -IC4265,G,13:30:22.97,-25:45:56.0,Hya -IC4266,G,13:29:05.63,+37:36:42.3,CVn -IC4267,G,13:30:36.10,-26:15:21.8,Hya -IC4268,G,13:29:12.33,+37:39:37.8,CVn -IC4269,G,13:29:21.04,+37:37:23.1,CVn -IC4270,G,13:30:49.09,-25:20:01.2,Hya -IC4271,GPair,13:29:21.40,+37:24:42.0,CVn -IC4271 NED01,G,13:29:21.44,+37:24:50.5,CVn -IC4271 NED02,G,13:29:21.31,+37:24:34.8,CVn -IC4272,G,13:31:16.56,-29:57:26.5,Hya -IC4273,G,13:31:29.86,-28:53:36.4,Hya -IC4274,Dup,13:33:32.91,-65:58:26.6,Mus -IC4275,G,13:31:51.30,-29:43:56.6,Hya -IC4276,G,13:32:06.26,-28:09:24.6,Hya -IC4277,G,13:30:16.72,+47:18:51.0,CVn -IC4278,G,13:30:27.55,+47:14:48.8,CVn -IC4279,G,13:32:30.97,-27:07:38.9,Hya -IC4280,G,13:32:53.40,-24:12:25.7,Hya -IC4281,G,13:32:38.25,-27:10:10.7,Hya -IC4282,G,13:31:19.77,+47:11:01.9,CVn -IC4283,G,13:32:10.68,+28:23:20.8,CVn -IC4284,G,13:31:31.94,+46:47:42.4,CVn -IC4285,G,13:31:45.54,+46:49:17.8,CVn -IC4286,G,13:33:35.65,-27:37:52.4,Hya -IC4287,GPair,13:32:39.00,+25:26:28.0,Com -IC4287 NED01,G,13:32:38.97,+25:26:28.0,Com -IC4287 NED02,G,13:32:39.66,+25:26:24.7,Com -IC4288,G,13:34:30.42,-27:18:15.2,Hya -IC4289,G,13:34:47.81,-27:07:37.5,Hya -IC4290,G,13:35:19.58,-28:01:18.5,Hya -IC4291,OCl,13:36:56.41,-62:05:35.2,Cen -IC4292,G,13:35:46.60,-27:40:25.9,Hya -IC4293,G,13:36:02.26,-25:52:56.3,Hya -IC4294,G,13:36:31.23,-28:46:52.1,Hya -IC4295,G,13:36:34.46,-29:05:22.0,Hya -IC4296,G,13:36:39.03,-33:57:57.0,Cen -IC4297,G,13:35:19.25,+26:25:29.2,Com -IC4298,G,13:36:34.73,-26:33:13.9,Hya -IC4299,G,13:36:47.48,-34:03:56.9,Cen -IC4300,G,13:35:25.20,+33:25:11.3,CVn -IC4301,G,13:35:35.80,+33:22:28.2,CVn -IC4302,G,13:35:35.95,+33:28:46.5,CVn -IC4303,G,13:37:18.22,-28:39:28.7,Hya -IC4304,G,13:35:57.88,+33:25:48.1,CVn -IC4305,G,13:35:58.37,+33:28:26.1,CVn -IC4306,G,13:36:19.64,+33:25:24.4,CVn -IC4307,G,13:36:36.17,+27:14:31.9,Boo -IC4308,Other,13:36:52.36,+32:44:00.1,CVn -IC4309,G,13:38:50.04,-29:39:45.8,Hya -IC4310,G,13:38:57.14,-25:50:44.6,Hya -IC4311,G,13:40:07.96,-51:02:10.1,Cen -IC4312,G,13:40:30.90,-51:04:15.2,Cen -IC4313,G,13:38:20.71,+26:45:35.2,Boo -IC4314,G,13:38:25.07,+26:44:33.0,Boo -IC4315,G,13:40:03.19,-25:28:28.4,Hya -IC4316,G,13:40:18.41,-28:53:32.0,Hya -IC4317,G,13:41:45.82,+27:06:22.9,Boo -IC4318,G,13:43:22.69,-28:58:04.6,Hya -IC4319,G,13:43:26.57,-29:48:12.5,Hya -IC4320,G,13:44:03.72,-27:13:54.1,Hya -IC4321,G,13:44:31.25,-30:08:22.5,Cen -IC4322,G,13:43:44.15,+25:23:31.7,Boo -IC4323,G,13:45:06.88,-28:39:04.5,Hya -IC4324,G,13:45:27.31,-30:13:38.0,Cen -IC4325,G,13:47:39.65,-29:26:03.7,Hya -IC4326,G,13:48:21.49,-29:37:35.0,Hya -IC4327,G,13:48:43.80,-30:13:03.0,Cen -IC4328,G,13:49:02.90,-29:56:13.3,Hya -IC4329,G,13:49:05.31,-30:17:45.1,Cen -IC4330,G,13:47:14.85,-28:19:54.5,Hya -IC4331,G,13:49:24.61,+25:09:16.8,Boo -IC4332,G,13:49:52.55,+25:11:27.2,Boo -IC4333,G,14:05:20.59,-84:16:21.7,Oct -IC4334,G,13:49:48.27,+29:41:38.7,CVn -IC4335,*,13:49:45.07,+33:40:25.1,CVn -IC4336,G,13:50:43.37,+39:42:24.4,CVn -IC4337,G,13:52:19.28,+14:16:18.9,Boo -IC4338,Dup,13:52:54.46,-01:06:52.7,Vir -IC4339,*,13:53:28.83,+37:32:20.5,CVn -IC4340,G,13:53:33.53,+37:23:13.1,CVn -IC4341,G,13:53:34.25,+37:31:20.1,CVn -IC4342,G,13:54:22.11,+25:09:11.1,Boo -IC4343,G,13:54:55.80,+25:07:21.5,Boo -IC4344,G,13:55:12.59,+25:01:17.2,Boo -IC4345,G,13:55:13.39,+25:03:06.5,Boo -IC4346,G,13:55:40.57,+25:09:11.0,Boo -IC4347,Dup,13:57:43.87,-39:58:42.3,Cen -IC4348,G,13:55:45.09,+25:12:11.2,Boo -IC4349,G,13:55:46.34,+25:09:06.9,Boo -IC4350,G,13:57:13.92,-25:14:44.9,Hya -IC4351,G,13:57:54.26,-29:18:56.6,Hya -IC4352,G,13:58:25.12,-34:31:02.4,Cen -IC4353,Other,13:57:03.70,+37:43:44.6,CVn -IC4354,G,13:58:30.90,-12:36:19.3,Vir -IC4355,G,13:58:06.12,+28:25:21.7,CVn -IC4356,G,13:58:45.10,+37:29:26.4,CVn -IC4357,G,14:00:43.69,+31:53:39.0,CVn -IC4358,G,14:03:34.18,-10:09:04.5,Vir -IC4359,G,14:05:23.33,-45:16:11.2,Cen -IC4360,G,14:04:21.34,-11:25:29.4,Vir -IC4361,G,14:04:07.47,-09:46:08.0,Vir -IC4362,G,14:05:22.22,-41:49:08.3,Cen -IC4363,G,14:04:12.30,-09:38:29.9,Vir -IC4364,G,14:04:19.72,-09:59:35.8,Vir -IC4365,Dup,14:03:47.34,+09:31:25.3,Boo -IC4366,G,14:05:11.48,-33:45:36.4,Cen -IC4367,G,14:05:36.58,-39:12:12.1,Cen -IC4368,G,14:04:46.44,-09:57:44.1,Vir -IC4369,G,14:04:05.87,+33:19:14.6,CVn -IC4370,G,14:04:09.86,+33:20:45.6,CVn -IC4371,G,14:04:10.87,+33:18:28.1,CVn -IC4372,G,14:05:46.09,-10:54:00.3,Vir -IC4373,G,14:05:43.17,+25:13:52.9,Boo -IC4374,G,14:07:29.76,-27:01:04.3,Hya -IC4375,Dup,14:08:03.11,-33:18:53.7,Cen -IC4376,Other,14:10:50.47,-30:47:33.9,Cen -IC4377,G,14:16:58.81,-75:38:49.9,Aps -IC4378,G,14:12:09.57,-34:15:55.2,Cen -IC4379,G,14:12:10.24,-34:16:21.8,Cen -IC4380,G,14:10:02.12,+37:32:59.5,Boo -IC4381,Dup,14:10:57.24,+25:29:50.0,Boo -IC4382,G,14:11:02.54,+25:31:09.7,Boo -IC4383,Dup,14:12:12.72,+15:52:08.0,Boo -IC4384,G,14:11:56.02,+27:06:50.1,Boo -IC4385,Other,14:14:31.72,-42:19:25.0,Cen -IC4386,G,14:15:02.37,-43:57:40.8,Cen -IC4387,G,14:15:01.75,-43:59:24.4,Cen -IC4388,G,14:16:03.48,-31:45:09.5,Cen -IC4389,G,14:16:46.20,-40:33:09.0,Cen -IC4390,G,14:16:59.20,-44:58:38.4,Cen -IC4391,G,14:16:27.04,-31:41:05.9,Cen -IC4392,Other,14:15:53.17,-13:03:04.5,Vir -IC4393,G,14:17:48.95,-31:20:56.7,Cen -IC4394,Other,14:16:22.36,+39:41:51.7,Boo -IC4395,G,14:17:21.08,+26:51:26.8,Boo -IC4396,G,14:17:30.24,+28:48:00.0,Boo -IC4397,G,14:17:58.72,+26:24:45.2,Boo -IC4398,G,14:18:03.44,+28:51:59.0,Boo -IC4399,G,14:18:23.96,+26:23:09.2,Boo -IC4400,Other,14:22:13.45,-60:34:11.3,Cen -IC4401,G,14:19:25.07,-04:29:20.9,Vir -IC4402,G,14:21:13.09,-46:17:52.4,Lup -IC4403,G,14:18:16.87,+31:39:13.8,Boo -IC4404,*,14:10:50.17,+78:37:41.9,UMi -IC4405,G,14:19:16.59,+26:17:54.9,Boo -IC4406,PN,14:22:26.48,-44:09:00.7,Lup -IC4407,G,14:23:36.86,-05:58:59.6,Vir -IC4408,G,14:21:13.09,+29:59:36.6,Boo -IC4409,G,14:21:33.30,+31:35:07.9,Boo -IC4410,G,14:22:13.95,+17:23:50.4,Boo -IC4411,Other,14:25:01.10,-35:01:14.2,Cen -IC4412,Dup,14:23:10.31,+26:15:56.9,Boo -IC4413,G,14:22:57.32,+37:31:38.7,Boo -IC4414,Dup,14:23:42.59,+28:20:52.3,Boo -IC4415,G,14:24:26.72,+16:38:22.6,Boo -IC4416,G,14:24:17.42,+29:38:08.9,Boo -IC4417,G,14:24:53.69,+17:02:16.5,Boo -IC4418,G,14:25:27.24,+25:31:34.6,Boo -IC4419,G,14:25:54.67,+16:37:57.1,Boo -IC4420,G,14:25:39.41,+25:22:42.1,Boo -IC4421,G,14:28:31.31,-37:35:01.5,Cen -IC4422,G,14:25:59.13,+30:28:25.4,Boo -IC4423,G,14:26:17.74,+26:14:46.1,Boo -IC4424,Dup,14:27:32.37,+04:49:17.8,Vir -IC4425,G,14:26:44.18,+27:11:22.3,Boo -IC4426,G,14:27:17.09,+16:49:51.5,Boo -IC4427,G,14:26:59.57,+26:51:50.3,Boo -IC4428,G,14:27:25.55,+16:11:27.0,Boo -IC4429,G,14:27:37.37,+16:53:59.5,Boo -IC4430,G,14:29:19.40,-33:27:18.4,Cen -IC4431,Dup,14:27:09.50,+30:56:53.6,Boo -IC4432,G,14:28:49.76,-39:33:07.9,Cen -IC4433,G,14:27:53.35,+16:11:44.0,Boo -IC4434,G,14:27:54.74,+16:12:25.8,Boo -IC4435,G,14:27:24.28,+37:28:17.2,Boo -IC4436,G,14:27:58.18,+26:30:16.2,Boo -IC4437,**,14:27:31.66,+41:30:11.6,Boo -IC4438,G,14:28:34.47,+17:20:04.4,Boo -IC4439,G,14:28:40.04,+17:01:29.1,Boo -IC4440,G,14:28:59.24,+17:19:14.1,Boo -IC4441,G,14:31:38.62,-43:25:06.4,Lup -IC4442,G,14:28:45.27,+28:57:51.3,Boo -IC4443,G,14:29:17.49,+16:10:53.6,Boo -IC4444,Dup,14:31:38.62,-43:25:06.4,Lup -IC4445,G,14:31:54.61,-46:02:07.1,Lup -IC4446,G,14:29:01.32,+37:27:47.3,Boo -IC4447,G,14:29:17.98,+30:49:56.0,Boo -IC4448,G,14:40:27.96,-78:48:33.2,Aps -IC4449,G,14:31:21.59,+15:14:27.1,Boo -IC4450,G,14:32:12.34,+28:33:24.6,Boo -IC4451,G,14:34:37.14,-36:17:09.0,Cen -IC4452,G,14:32:27.43,+27:25:38.7,Boo -IC4453,G,14:34:28.65,-27:31:06.8,Hya -IC4454,G,14:33:16.69,+17:42:42.5,Boo -IC4455,Dup,14:33:43.62,-14:37:10.9,Lib -IC4456,G,14:34:09.17,+16:11:02.5,Boo -IC4457,G,14:34:28.88,+18:13:28.9,Boo -IC4458,G,14:38:05.87,-39:30:21.5,Cen -IC4459,G,14:34:32.20,+30:58:28.8,Boo -IC4460,G,14:34:36.53,+30:16:45.8,Boo -IC4461,G,14:35:00.38,+26:31:54.9,Boo -IC4462,G,14:35:01.90,+26:32:38.1,Boo -IC4463,G,14:35:49.00,+16:01:09.4,Boo -IC4464,G,14:37:49.00,-36:52:40.8,Cen -IC4465,G,14:35:51.16,+15:34:22.5,Boo -IC4466,G,14:36:48.11,+18:20:37.6,Boo -IC4467,G,14:36:53.67,+18:22:14.3,Boo -IC4468,G,14:38:26.74,-22:22:03.4,Lib -IC4469,G,14:37:20.49,+18:14:57.6,Boo -IC4470,G,14:28:22.89,+78:53:08.2,UMi -IC4471,Dup,14:36:32.06,+41:41:09.2,Boo -IC4472,G,14:40:10.84,-44:18:52.5,Lup -IC4473,GTrpl,14:37:54.14,+15:51:42.0,Boo -IC4473 NED01,G,14:37:54.39,+15:51:38.5,Boo -IC4473 NED02,G,14:37:54.05,+15:51:49.2,Boo -IC4473 NED03,G,14:37:54.22,+15:52:12.6,Boo -IC4474,G,14:38:22.30,+23:25:43.5,Boo -IC4475,G,14:38:23.26,+23:20:00.8,Boo -IC4476,G,14:39:51.85,-16:14:41.1,Lib -IC4477,G,14:38:35.24,+28:27:33.8,Boo -IC4478,G,14:39:12.69,+15:52:38.6,Boo -IC4479,G,14:38:45.90,+28:30:19.6,Boo -IC4480,G,14:39:45.63,+18:29:32.5,Boo -IC4481,G,14:40:10.11,+16:08:29.2,Boo -IC4482,G,14:40:12.46,+18:56:35.8,Boo -IC4483,G,14:40:19.51,+16:41:06.8,Boo -IC4484,G,14:47:43.88,-73:18:21.4,Aps -IC4485,G,14:40:31.46,+28:40:07.9,Boo -IC4486,G,14:41:40.79,+18:33:26.0,Boo -IC4487,G,14:41:52.19,+18:34:37.2,Boo -IC4488,G,14:42:52.58,+18:37:13.0,Boo -IC4489,G,14:43:15.95,+18:31:43.0,Boo -IC4490,**,14:45:21.27,-36:10:24.1,Cen -IC4491,Dup,14:47:25.69,-13:42:58.1,Lib -IC4492,G,14:42:33.87,+37:27:08.4,Boo -IC4493,Dup,14:44:20.80,+12:07:47.0,Boo -IC4494,G,14:44:25.54,+15:33:05.0,Boo -IC4495,G,14:44:14.57,+23:33:30.2,Boo -IC4496,G,14:43:54.58,+33:24:23.6,Boo -IC4497,G,14:44:20.86,+28:33:03.7,Boo -IC4498,G,14:45:00.79,+26:18:04.1,Boo -IC4499,GCl,15:00:19.25,-82:12:48.6,Aps -IC4500,G,14:44:35.66,+37:28:56.9,Boo -IC4501,G,14:47:25.47,-22:24:21.7,Lib -IC4502,G,14:45:15.83,+37:18:01.3,Boo -IC4503,G,14:46:39.51,+16:08:47.2,Boo -IC4504,G,14:46:36.98,+31:41:57.1,Boo -IC4505,G,14:46:33.37,+33:24:31.0,Boo -IC4506,G,14:46:39.94,+33:24:04.4,Boo -IC4507,G,14:47:42.16,+18:27:20.6,Boo -IC4508,G,14:47:51.41,+31:46:05.8,Boo -IC4509,G,14:48:27.06,+31:47:29.7,Boo -IC4510,Other,14:50:40.71,-20:43:51.2,Lib -IC4511,Other,14:52:05.28,-40:29:41.7,Cen -IC4512,G,14:49:54.30,+27:42:02.9,Boo -IC4513,Other,14:52:16.83,-20:43:40.5,Lib -IC4514,G,14:50:55.44,+27:34:42.7,Boo -IC4515,G,14:51:06.67,+37:29:41.3,Boo -IC4516,G,14:54:23.45,+16:21:19.0,Boo -IC4517,G,14:54:35.09,+23:38:35.8,Boo -IC4518,GPair,14:57:42.90,-43:07:54.0,Lup -IC4518A,G,14:57:41.18,-43:07:55.6,Lup -IC4518B,G,14:57:44.46,-43:07:52.9,Lup -IC4519,G,14:54:44.54,+37:24:45.8,Boo -IC4520,G,14:55:07.01,+33:43:28.0,Boo -IC4521,G,14:59:27.45,+25:35:02.7,Boo -IC4522,G,15:11:28.92,-75:51:35.9,Aps -IC4523,G,15:05:10.54,-43:30:34.3,Lup -IC4524,G,15:02:06.36,+25:36:05.4,Boo -IC4525,G,15:02:24.86,+25:38:18.6,Boo -IC4526,G,15:02:38.20,+23:21:01.6,Boo -IC4527,G,15:05:41.02,-42:26:58.3,Lup -IC4528,G,15:01:33.37,+49:06:44.9,Boo -IC4529,G,15:06:25.98,-43:13:57.5,Lup -IC4530,G,15:03:45.31,+26:06:03.1,Boo -IC4531,G,15:04:26.44,+23:24:53.8,Boo -IC4532,G,15:04:53.81,+23:15:23.8,Boo -IC4533,G,15:04:30.34,+27:47:34.3,Boo -IC4534,G,15:06:41.77,+23:38:30.9,Boo -IC4535,G,15:08:41.60,+37:34:13.2,Boo -IC4536,G,15:13:17.23,-18:08:14.2,Lib -IC4537,G,15:17:32.44,+02:02:50.6,Se1 -IC4538,G,15:21:11.61,-23:39:30.3,Lib -IC4539,G,15:18:31.15,+32:23:34.3,CrB -IC4540,Other,15:20:03.11,+01:47:11.7,Se1 -IC4541,G,15:29:55.57,-70:35:05.8,Aps -IC4542,G,15:22:05.39,+33:08:54.9,Boo -IC4543,Dup,15:24:59.53,+13:26:42.3,Se1 -IC4544,Nova,15:29:22.99,-50:35:00.4,Nor -IC4545,G,15:41:27.78,-81:37:33.4,Aps -IC4546,G,15:26:58.41,+28:51:09.4,CrB -IC4547,G,15:27:15.07,+28:47:20.3,CrB -IC4548,G,15:27:24.02,+28:50:59.4,CrB -IC4549,G,15:29:14.57,+32:49:32.0,Boo -IC4550,Dup,15:35:28.57,-50:39:35.0,Nor -IC4551,Dup,15:37:36.22,+05:58:26.5,Se1 -IC4552,G,15:38:54.89,+04:34:59.0,Se1 -IC4553,G,15:34:57.25,+23:30:11.3,Se1 -IC4554,G,15:35:04.83,+23:28:45.4,Se1 -IC4555,G,15:48:14.81,-78:10:43.9,Aps -IC4556,G,15:35:22.45,+25:17:49.7,Se1 -IC4557,G,15:34:36.93,+39:43:44.0,Boo -IC4558,G,15:35:46.20,+25:20:45.2,Se1 -IC4559,G,15:35:53.52,+25:20:28.1,Se1 -IC4560,G,15:35:54.08,+39:48:50.8,Boo -IC4561,G,15:36:47.07,+25:25:00.5,Se1 -IC4562,G,15:35:57.00,+43:29:35.7,Boo -IC4563,G,15:36:03.68,+39:49:53.5,Boo -IC4564,G,15:36:27.06,+43:31:07.6,Boo -IC4565,G,15:36:35.13,+43:25:29.6,Boo -IC4566,G,15:36:42.16,+43:32:21.6,Boo -IC4567,G,15:37:13.27,+43:17:53.9,Boo -IC4568,G,15:40:07.70,+28:09:08.2,CrB -IC4569,G,15:40:48.38,+28:17:31.5,CrB -IC4570,G,15:41:22.59,+28:13:47.1,CrB -IC4571,G,15:48:51.50,-67:19:24.5,TrA -IC4572,G,15:41:54.19,+28:08:02.4,CrB -IC4573,G,15:42:12.34,+23:47:59.9,Se1 -IC4574,G,15:41:59.24,+28:14:25.2,CrB -IC4575,G,15:42:19.65,+23:48:27.5,Se1 -IC4576,G,15:42:35.50,+23:40:12.2,Se1 -IC4577,G,15:42:45.56,+23:47:33.5,Se1 -IC4578,G,15:53:10.66,-74:49:31.0,Aps -IC4579,G,15:42:51.51,+23:46:23.4,Se1 -IC4580,G,15:43:14.24,+28:21:24.6,CrB -IC4581,G,15:44:01.52,+28:16:37.3,CrB -IC4582,G,15:45:39.45,+28:05:19.2,CrB -IC4583,G,15:46:21.94,+23:48:32.5,Se1 -IC4584,G,16:00:12.26,-66:22:59.8,TrA -IC4585,G,16:00:17.64,-66:19:20.2,TrA -IC4586,Dup,15:55:57.40,+05:55:54.8,Se1 -IC4587,G,15:59:51.61,+25:56:26.3,CrB -IC4588,G,16:05:04.25,+23:55:01.7,Se1 -IC4589,*,16:07:24.59,-06:23:07.1,Oph -IC4590,G,16:08:21.15,+28:28:43.3,CrB -IC4591,HII,16:12:18.16,-27:55:39.9,Sco -IC4592,RfN,16:11:58.67,-19:27:16.8,Sco -IC4593,PN,16:11:44.50,+12:04:17.0,Her -IC4594,Dup,16:11:22.59,+23:57:54.0,Her -IC4595,G,16:20:44.44,-70:08:33.4,TrA -IC4596,G,16:16:03.61,-22:37:31.4,Sco -IC4597,G,16:17:39.73,-34:21:57.4,Sco -IC4598,G,16:18:13.31,-31:26:34.4,Sco -IC4599,PN,16:19:23.17,-42:15:36.5,Sco -IC4600,Other,16:18:08.67,-22:47:07.9,Sco -IC4601,Neb,16:20:17.80,-20:05:14.3,Sco -IC4602,Dup,16:23:38.84,+11:47:10.5,Her -IC4603,Neb,16:25:24.48,-24:28:06.1,Oph -IC4604,Neb,16:25:31.17,-23:26:11.7,Oph -IC4605,Neb,16:30:12.48,-25:06:54.6,Sco -IC4606,Other,16:31:33.89,-26:03:23.5,Sco -IC4607,G,16:30:15.87,+24:34:27.7,Her -IC4608,G,16:46:53.98,-77:29:19.3,Aps -IC4609,G,16:33:01.62,+22:47:50.6,Her -IC4610,G,16:33:39.16,+39:15:27.5,Her -IC4611,G,16:33:42.34,+39:11:06.5,Her -IC4612,G,16:33:49.62,+39:15:47.5,Her -IC4613,Other,16:37:10.15,+36:07:50.1,Her -IC4614,G,16:37:47.18,+36:06:54.0,Her -IC4615,Dup,16:37:53.92,+36:04:23.0,Her -IC4616,Dup,16:37:59.88,+35:59:43.8,Her -IC4617,G,16:42:08.06,+36:41:02.7,Her -IC4618,G,16:57:50.01,-76:59:34.7,Aps -IC4619,G,16:44:11.12,+17:45:33.0,Her -IC4620,G,16:48:30.04,+19:18:19.4,Her -IC4621,G,16:50:51.17,+08:47:01.2,Her -IC4622,Other,16:52:08.31,-16:14:10.5,Oph -IC4623,G,16:51:05.34,+22:31:38.7,Her -IC4624,G,16:51:33.54,+17:26:56.8,Her -IC4625,Dup,16:52:58.87,+02:24:03.3,Oph -IC4626,**,16:53:21.43,+02:20:19.4,Oph -IC4627,G,16:54:08.75,-07:38:07.2,Oph -IC4628,Neb,16:56:58.43,-40:27:03.5,Sco -IC4629,Other,16:56:09.99,-16:42:35.6,Oph -IC4630,G,16:55:09.58,+26:39:46.5,Her -IC4631,Other,17:11:00.03,-77:36:12.8,Aps -IC4632,Other,16:58:32.13,+22:54:56.0,Her -IC4633,G,17:13:47.04,-77:32:10.3,Aps -IC4634,PN,17:01:33.59,-21:49:33.5,Oph -IC4635,G,17:15:39.19,-77:29:22.3,Aps -IC4636,*,16:59:06.82,+47:11:43.5,Her -IC4637,PN,17:05:10.82,-40:53:10.9,Sco -IC4638,G,17:01:13.70,+33:30:47.5,Her -IC4639,G,17:02:54.75,+22:55:48.1,Her -IC4640,G,17:23:58.35,-80:03:50.7,Aps -IC4641,G,17:24:10.32,-80:08:50.7,Aps -IC4642,PN,17:11:45.22,-55:23:56.4,Ara -IC4643,Dup,17:08:32.74,+42:20:20.8,Her -IC4644,G,17:24:36.90,-73:56:24.4,Aps -IC4645,G,17:14:43.13,+43:06:14.8,Her -IC4646,G,17:23:53.19,-59:59:57.8,Ara -IC4647,G,17:26:03.74,-80:11:42.4,Aps -IC4648,**,17:16:10.03,+43:51:45.3,Her -IC4649,Dup,17:15:50.38,+57:22:00.5,Dra -IC4650,G,17:15:47.41,+57:18:06.8,Dra -IC4651,OCl,17:24:49.14,-49:56:17.7,Ara -IC4652,G,17:26:26.67,-59:43:41.9,Ara -IC4653,G,17:27:07.18,-60:52:46.5,Ara -IC4654,G,17:37:07.89,-74:22:53.2,Aps -IC4655,Other,17:34:35.62,-60:43:16.8,Ara -IC4656,G,17:37:43.87,-63:43:46.8,Ara -IC4657,Other,17:32:42.70,-17:31:29.4,Oph -IC4658,Other,17:36:10.81,-59:35:05.7,Ara -IC4659,Other,17:34:12.23,-17:55:40.9,Oph -IC4660,G,17:21:45.18,+75:50:54.6,UMi -IC4661,G,17:51:02.76,-74:01:56.6,Aps -IC4662,G,17:47:08.87,-64:38:30.3,Pav -IC4663,PN,17:45:28.20,-44:54:17.5,Sco -IC4664,G,17:48:58.59,-63:15:16.3,Pav -IC4665,OCl,17:46:27.18,+05:38:55.4,Oph -IC4666,*,17:46:02.36,+55:46:32.1,Dra -IC4667,Other,17:46:18.96,+55:52:32.3,Dra -IC4668,*,17:46:59.41,+57:24:02.5,Dra -IC4669,G,17:47:12.97,+61:26:03.2,Dra -IC4670,PN,17:55:07.00,-21:44:39.3,Sgr -IC4671,Other,17:55:07.90,-10:17:09.3,Se2 -IC4672,G,18:02:14.71,-62:49:57.0,Pav -IC4673,PN,18:03:18.40,-27:06:22.0,Sgr -IC4674,G,18:08:13.08,-62:23:43.8,Pav -IC4675,Other,18:03:10.68,-09:15:34.1,Oph -IC4676,G,18:02:52.99,+11:49:23.3,Oph -IC4677,Other,17:58:15.77,+66:37:59.8,Dra -IC4678,Neb,18:06:33.47,-23:57:16.0,Sgr -IC4679,G,18:11:24.49,-56:15:15.8,Tel -IC4680,G,18:13:29.64,-64:28:36.2,Pav -IC4681,*,18:08:20.00,-23:25:55.3,Sgr -IC4682,G,18:16:25.71,-71:34:52.7,Pav -IC4683,Other,18:09:00.93,-26:23:27.4,Sgr -IC4684,RfN,18:09:08.44,-23:26:07.6,Sgr -IC4685,Neb,18:09:17.50,-23:59:14.1,Sgr -IC4686,G,18:13:38.64,-57:43:57.1,Pav -IC4687,G,18:13:39.63,-57:43:31.3,Pav -IC4688,G,18:08:11.88,+11:42:44.0,Oph -IC4689,G,18:13:40.28,-57:44:53.5,Pav -IC4690,Dup,18:16:55.37,-19:46:37.5,Sgr -IC4691,G,18:08:45.70,+11:49:48.3,Oph -IC4692,G,18:14:49.97,-58:41:37.6,Pav -IC4693,Other,18:09:10.83,+17:20:52.4,Her -IC4694,G,18:15:26.45,-58:12:32.1,Pav -IC4695,**,18:17:23.47,-58:55:31.7,Pav -IC4696,G,18:20:17.91,-64:44:02.7,Pav -IC4697,G,18:12:26.91,+25:25:37.9,Her -IC4698,G,18:20:59.93,-63:20:52.6,Pav -IC4699,PN,18:18:32.79,-45:58:59.1,Tel -IC4700,Dup,18:17:04.99,-19:51:57.8,Sgr -IC4701,Neb,18:16:35.74,-16:38:53.8,Sgr -IC4702,G,18:23:03.82,-59:14:20.2,Pav -IC4703,Neb,18:18:56.22,-13:50:43.4,Se2 -IC4704,G,18:27:53.64,-71:36:35.5,Pav -IC4705,G,18:28:10.32,-71:41:38.2,Pav -IC4706,Neb,18:19:36.94,-16:01:52.6,Sgr -IC4707,Neb,18:19:53.91,-16:00:33.3,Sgr -IC4708,G,18:13:46.21,+61:09:26.2,Dra -IC4709,G,18:24:19.39,-56:22:09.0,Tel -IC4710,G,18:28:37.97,-66:58:56.2,Pav -IC4711,Other,18:28:06.82,-64:56:40.1,Pav -IC4712,G,18:31:06.92,-71:41:37.2,Pav -IC4713,G,18:29:59.34,-67:13:27.8,Pav -IC4714,G,18:30:55.75,-66:39:06.3,Pav -IC4715,*Ass,18:16:56.12,-18:30:52.4,Sgr -IC4716,G,18:32:45.04,-56:57:43.2,Pav -IC4717,G,18:33:17.25,-57:58:32.6,Pav -IC4718,G,18:33:50.16,-60:07:44.8,Pav -IC4719,G,18:33:11.54,-56:43:55.9,Tel -IC4720,G,18:33:32.50,-58:24:19.3,Pav -IC4721,G,18:34:24.76,-58:29:47.8,Pav -IC4722,G,18:34:31.53,-57:47:34.0,Pav -IC4723,G,18:35:56.26,-63:22:36.0,Pav -IC4724,G,18:38:40.28,-70:07:32.4,Pav -IC4725,OCl,18:31:46.77,-19:06:53.8,Sgr -IC4726,G,18:36:58.81,-62:51:15.6,Pav -IC4727,G,18:37:56.06,-62:42:02.4,Pav -IC4728,G,18:37:57.00,-62:31:51.3,Pav -IC4729,G,18:39:56.43,-67:25:32.5,Pav -IC4730,G,18:38:50.17,-63:20:59.7,Pav -IC4731,G,18:38:42.98,-62:56:34.9,Pav -IC4732,PN,18:33:54.60,-22:38:41.0,Sgr -IC4733,*,18:26:38.18,+64:58:02.7,Dra -IC4734,G,18:38:25.70,-57:29:25.6,Pav -IC4735,G,18:39:49.94,-62:57:21.7,Pav -IC4736,G,18:38:39.81,-57:53:35.8,Pav -IC4737,G,18:39:58.40,-62:35:52.7,Pav -IC4738,G,18:40:26.92,-61:54:08.5,Pav -IC4739,G,18:40:50.98,-61:54:05.6,Pav -IC4740,G,18:43:00.58,-68:21:35.8,Pav -IC4741,G,18:41:43.43,-63:56:53.5,Pav -IC4742,G,18:41:52.57,-63:51:43.5,Pav -IC4743,G,18:41:29.15,-61:46:20.0,Pav -IC4744,G,18:41:54.89,-63:13:25.9,Pav -IC4745,G,18:42:35.88,-64:56:34.5,Pav -IC4746,G,18:45:54.51,-72:40:05.6,Pav -IC4747,G,18:45:57.34,-72:37:48.1,Pav -IC4748,G,18:42:45.96,-64:04:21.6,Pav -IC4749,G,18:42:49.49,-63:12:30.3,Pav -IC4750,G,18:43:02.68,-62:58:17.3,Pav -IC4751,G,18:43:19.34,-62:06:44.2,Pav -IC4752,G,18:43:46.73,-64:04:55.6,Pav -IC4753,G,18:43:32.62,-62:06:28.6,Pav -IC4754,G,18:44:00.23,-61:59:24.2,Pav -IC4755,G,18:45:01.15,-63:41:32.0,Pav -IC4756,OCl,18:38:51.51,+05:27:43.8,Se2 -IC4757,G,18:43:55.75,-57:10:03.2,Pav -IC4758,G,18:46:18.27,-65:45:24.0,Pav -IC4759,GPair,18:45:41.00,-63:05:07.0,Pav -IC4759 NED01,G,18:45:40.72,-63:05:05.2,Pav -IC4759 NED02,G,18:45:41.30,-63:05:13.2,Pav -IC4760,G,18:45:45.95,-62:57:30.1,Pav -IC4761,G,18:43:55.60,-52:51:10.9,Tel -IC4762,**,18:32:28.53,+67:51:28.9,Dra -IC4763,Dup,18:33:30.50,+67:08:14.3,Dra -IC4764,G,18:47:07.57,-63:29:04.4,Pav -IC4765,G,18:47:17.93,-63:19:52.8,Pav -IC4766,G,18:47:35.77,-63:17:31.8,Pav -IC4767,G,18:47:41.73,-63:24:20.4,Pav -IC4768,OCl,18:41:28.19,-05:32:05.4,Sct -IC4769,G,18:47:44.05,-63:09:25.2,Pav -IC4770,G,18:48:10.33,-63:23:00.4,Pav -IC4771,G,18:48:23.84,-63:14:51.6,Pav -IC4772,G,18:39:56.50,+40:01:35.0,Lyr -IC4773,G,18:51:20.82,-69:55:29.1,Pav -IC4774,G,18:48:10.65,-57:56:08.5,Pav -IC4775,G,18:48:26.29,-57:11:01.4,Pav -IC4776,PN,18:45:50.70,-33:20:35.0,Sgr -IC4777,G,18:48:11.30,-53:08:51.4,Tel -IC4778,G,18:50:00.37,-61:43:10.2,Pav -IC4779,G,18:50:30.43,-63:00:46.4,Pav -IC4780,G,18:49:56.09,-59:15:10.3,Pav -IC4781,G,18:51:37.64,-62:47:33.4,Pav -IC4782,G,18:50:54.87,-55:29:28.4,Tel -IC4783,G,18:51:33.37,-58:48:47.4,Pav -IC4784,G,18:52:48.02,-63:15:34.6,Pav -IC4785,G,18:52:55.23,-59:15:19.5,Pav -IC4786,G,18:52:44.57,-56:41:40.6,Tel -IC4787,G,18:56:04.66,-68:40:56.5,Pav -IC4788,G,18:54:41.02,-63:27:08.6,Pav -IC4789,G,18:56:18.49,-68:34:01.6,Pav -IC4790,G,18:56:32.21,-64:55:44.1,Pav -IC4791,G,18:49:01.18,+19:19:52.0,Her -IC4792,G,18:55:41.76,-56:24:13.7,Tel -IC4793,G,18:56:55.60,-61:23:59.2,Pav -IC4794,G,18:57:09.63,-62:05:27.4,Pav -IC4795,G,18:57:16.36,-61:36:32.7,Pav -IC4796,G,18:56:27.84,-54:12:50.2,Tel -IC4797,G,18:56:29.68,-54:18:20.8,Tel -IC4798,G,18:58:20.89,-62:07:06.3,Pav -IC4799,G,18:58:56.59,-63:55:51.5,Pav -IC4800,G,18:58:43.52,-63:08:21.2,Pav -IC4801,G,18:59:38.39,-64:40:30.5,Pav -IC4802,Other,18:55:07.09,-22:41:53.9,Sgr -IC4803,G,19:00:39.87,-62:03:54.5,Pav -IC4804,G,19:01:07.38,-61:50:00.0,Pav -IC4805,G,19:02:01.42,-63:02:51.5,Pav -IC4806,G,19:01:30.69,-57:31:55.4,Pav -IC4807,G,19:02:17.66,-56:55:52.3,Pav -IC4808,G,19:01:07.62,-45:18:49.4,CrA -IC4809,G,19:04:05.38,-62:11:38.7,Pav -IC4810,G,19:02:59.71,-56:09:35.0,Tel -IC4811,G,19:05:44.67,-67:08:01.8,Pav -IC4812,Neb,19:01:03.63,-37:03:37.2,CrA -IC4813,G,19:05:41.71,-66:31:19.4,Pav -IC4814,G,19:04:58.89,-58:34:45.8,Pav -IC4815,G,19:06:50.61,-61:42:04.9,Pav -IC4816,Nova,19:01:50.52,-13:09:42.6,Sgr -IC4817,G,19:06:12.35,-56:09:33.7,Tel -IC4818,G,19:06:03.04,-55:08:12.6,Tel -IC4819,G,19:07:07.30,-59:28:01.0,Pav -IC4820,G,19:09:13.51,-63:27:55.8,Pav -IC4821,G,19:09:32.01,-55:01:02.3,Tel -IC4822,G,19:14:45.49,-72:26:28.1,Pav -IC4823,G,19:12:15.75,-63:58:33.1,Pav -IC4824,G,19:13:14.07,-62:05:17.8,Pav -IC4825,G,19:17:15.63,-72:44:55.5,Pav -IC4826,G,19:12:21.20,-57:12:08.3,Pav -IC4827,G,19:13:21.24,-60:51:36.6,Pav -IC4828,G,19:13:40.69,-62:04:57.4,Pav -IC4829,G,19:12:33.70,-56:32:24.9,Tel -IC4830,G,19:13:48.56,-59:17:39.7,Pav -IC4831,G,19:14:43.84,-62:16:21.4,Pav -IC4832,G,19:14:03.87,-56:36:38.6,Tel -IC4833,G,19:15:41.15,-62:19:47.8,Pav -IC4834,G,19:16:31.33,-64:00:22.2,Pav -IC4835,G,19:15:27.48,-58:14:16.8,Pav -IC4836,G,19:16:17.93,-60:12:01.2,Pav -IC4837,G,19:15:14.64,-54:39:41.1,Tel -IC4838,G,19:16:46.11,-61:36:52.2,Pav -IC4839,G,19:15:34.12,-54:37:36.1,Tel -IC4840,G,19:15:51.69,-56:12:32.7,Tel -IC4841,G,19:20:42.83,-72:13:36.4,Pav -IC4842,G,19:19:24.47,-60:38:39.3,Pav -IC4843,G,19:19:21.69,-59:18:31.9,Pav -IC4844,G,19:19:02.64,-56:01:37.8,Tel -IC4845,G,19:20:22.49,-60:23:21.0,Pav -IC4846,PN,19:16:28.30,-09:02:37.0,Aql -IC4847,G,19:23:32.01,-65:30:24.0,Pav -IC4848,G,19:22:54.57,-56:46:50.9,Pav -IC4849,G,19:25:35.99,-62:55:57.8,Pav -IC4850,Nova,19:20:24.09,-00:08:00.8,Aql -IC4851,G,19:25:29.44,-57:40:13.6,Pav -IC4852,G,19:26:25.34,-60:20:09.8,Pav -IC4853,G,19:30:47.31,-71:04:12.1,Pav -IC4854,G,19:27:21.15,-59:18:55.6,Pav -IC4855,Dup,19:27:21.15,-59:18:55.6,Pav -IC4856,G,19:27:30.53,-54:54:30.7,Tel -IC4857,G,19:28:39.18,-58:46:04.5,Pav -IC4858,Dup,19:28:39.18,-58:46:04.5,Pav -IC4859,G,19:30:46.66,-66:18:53.0,Pav -IC4860,G,19:31:27.51,-67:22:08.5,Pav -IC4861,G,19:29:16.72,-57:34:29.9,Pav -IC4862,G,19:31:40.35,-67:19:22.4,Pav -IC4863,**,19:27:51.69,-36:13:00.8,Sgr -IC4864,G,19:40:06.17,-77:33:29.4,Oct -IC4865,**,19:30:50.34,-46:41:54.4,Tel -IC4866,G,19:34:34.75,-61:08:44.8,Pav -IC4867,Dup,19:26:31.98,+50:07:31.0,Cyg -IC4868,**,19:33:33.40,-45:53:33.7,Tel -IC4869,G,19:36:02.33,-61:01:40.3,Pav -IC4870,G,19:37:37.60,-65:48:42.6,Pav -IC4871,G,19:35:42.23,-57:31:09.2,Pav -IC4872,Dup,19:35:42.23,-57:31:09.2,Pav -IC4873,G,19:34:54.69,-46:08:09.0,Tel -IC4874,G,19:36:21.54,-47:15:57.4,Tel -IC4875,G,19:37:38.49,-52:04:32.6,Tel -IC4876,G,19:37:42.77,-52:50:33.9,Tel -IC4877,G,19:37:55.89,-51:59:29.0,Tel -IC4878,G,19:38:50.07,-58:13:35.2,Pav -IC4879,G,19:39:36.45,-52:22:07.6,Tel -IC4880,G,19:40:30.91,-56:24:36.6,Tel -IC4881,G,19:40:26.02,-55:51:27.8,Tel -IC4882,G,19:40:23.31,-55:11:48.9,Tel -IC4883,G,19:42:00.53,-55:32:43.9,Tel -IC4884,G,19:42:41.10,-58:07:42.5,Pav -IC4885,G,19:43:52.04,-60:39:06.3,Pav -IC4886,G,19:43:14.55,-51:48:27.0,Tel -IC4887,G,19:48:21.29,-69:35:13.9,Pav -IC4888,G,19:44:52.24,-54:27:23.9,Tel -IC4889,G,19:45:15.15,-54:20:38.9,Tel -IC4890,G,19:45:35.46,-56:32:43.6,Tel -IC4891,Dup,19:45:15.15,-54:20:38.9,Tel -IC4892,G,19:49:31.61,-70:13:38.8,Pav -IC4893,G,19:50:33.36,-72:30:36.0,Pav -IC4894,G,19:46:59.11,-51:50:47.4,Tel -IC4895,Dup,19:44:57.74,-14:48:12.4,Sgr -IC4896,G,19:49:04.91,-58:58:53.2,Pav -IC4897,G,19:49:19.70,-51:52:04.8,Tel -IC4898,Other,19:47:46.18,-33:19:14.1,Sgr -IC4899,G,19:54:26.75,-70:35:23.1,Pav -IC4900,G,19:50:22.10,-51:20:45.0,Tel -IC4901,G,19:54:23.53,-58:42:48.8,Pav -IC4902,G,19:54:24.11,-56:22:44.7,Tel -IC4903,G,19:58:13.42,-70:27:12.3,Pav -IC4904,G,19:58:38.81,-70:11:03.1,Pav -IC4905,G,19:56:05.87,-61:13:15.9,Pav -IC4906,G,19:56:47.60,-60:28:05.1,Pav -IC4907,G,19:56:13.06,-52:27:14.3,Tel -IC4908,G,19:56:56.67,-55:47:30.2,Tel -IC4909,G,19:56:45.60,-50:03:20.7,Tel -IC4910,G,19:57:46.99,-56:51:47.7,Pav -IC4911,G,19:57:41.83,-51:59:11.3,Tel -IC4912,G,20:06:49.69,-77:21:27.0,Oct -IC4913,G,19:56:47.64,-37:19:42.1,Sgr -IC4914,G,19:57:56.51,-50:07:53.4,Tel -IC4915,G,19:58:31.94,-52:38:32.0,Tel -IC4916,G,19:58:19.17,-50:16:19.6,Tel -IC4917,G,19:58:54.77,-52:16:23.5,Tel -IC4918,G,19:59:13.19,-52:16:30.5,Tel -IC4919,G,20:00:09.05,-55:22:24.2,Tel -IC4920,G,20:00:08.81,-53:23:01.6,Tel -IC4921,G,20:03:19.60,-67:49:36.7,Pav -IC4922,Other,19:59:29.37,-40:21:47.1,Sgr -IC4923,G,20:00:57.37,-52:37:54.0,Tel -IC4924,Other,19:59:51.43,-41:32:45.8,Sgr -IC4925,G,20:01:09.87,-52:51:57.0,Tel -IC4926,G,20:00:12.15,-38:34:42.9,Sgr -IC4927,G,20:01:49.48,-53:55:03.9,Tel -IC4928,G,20:10:12.00,-77:18:32.3,Oct -IC4929,G,20:06:41.70,-71:41:00.8,Pav -IC4930,Other,20:02:26.46,-54:18:30.9,Tel -IC4931,G,20:00:50.36,-38:34:30.2,Sgr -IC4932,G,20:02:15.48,-52:50:46.2,Tel -IC4933,G,20:03:29.04,-54:58:47.8,Tel -IC4934,G,20:07:14.85,-69:28:45.1,Pav -IC4935,G,20:04:34.12,-57:35:53.4,Pav -IC4936,G,20:05:52.20,-61:25:42.7,Pav -IC4937,G,20:05:17.58,-56:15:22.3,Tel -IC4938,G,20:06:11.70,-60:12:41.8,Pav -IC4939,G,20:07:11.04,-60:44:19.7,Pav -IC4940,Other,20:05:43.62,-44:41:59.8,Sgr -IC4941,G,20:06:58.61,-53:39:08.3,Tel -IC4942,G,20:06:49.38,-52:36:35.7,Tel -IC4943,G,20:06:28.26,-48:22:32.2,Tel -IC4944,G,20:07:08.84,-54:26:48.8,Tel -IC4945,Dup,20:11:16.85,-71:00:46.6,Pav -IC4946,G,20:23:58.07,-43:59:43.0,Sgr -IC4947,G,20:07:31.75,-53:08:32.8,Tel -IC4948,Dup,20:24:28.14,-43:39:12.7,Sgr -IC4949,Dup,20:07:19.48,-48:22:12.8,Tel -IC4950,G,20:08:27.36,-56:09:42.6,Tel -IC4951,G,20:09:31.77,-61:51:01.7,Pav -IC4952,G,20:08:37.65,-55:27:13.2,Tel -IC4953,G,20:09:59.92,-62:47:32.0,Pav -IC4954,HII,20:04:45.02,+29:15:10.1,Vul -IC4955,Neb,20:04:52.55,+29:11:33.4,Vul -IC4956,G,20:11:31.26,-45:35:35.6,Tel -IC4957,G,20:09:35.63,-55:42:33.5,Tel -IC4958,G,20:15:35.34,-72:42:40.3,Pav -IC4959,Other,20:10:57.22,-53:05:22.9,Tel -IC4960,G,20:15:23.88,-70:32:15.9,Pav -IC4961,G,20:11:28.59,-53:07:30.4,Tel -IC4962,G,20:16:42.49,-71:07:46.4,Pav -IC4963,G,20:12:05.49,-55:14:45.2,Tel -IC4964,G,20:17:23.93,-73:53:08.3,Pav -IC4965,G,20:12:27.32,-56:49:36.4,Pav -IC4966,Other,20:12:16.42,-53:37:09.1,Tel -IC4967,G,20:16:23.15,-70:33:52.9,Pav -IC4968,G,20:14:50.23,-64:47:54.2,Pav -IC4969,G,20:12:56.30,-53:55:12.2,Tel -IC4970,G,20:16:57.35,-70:44:59.1,Pav -IC4971,G,20:17:02.86,-70:37:15.5,Pav -IC4972,G,20:17:42.72,-70:54:54.4,Pav -IC4973,G,20:14:34.13,-58:22:16.0,Pav -IC4974,GPair,20:15:26.50,-61:51:37.0,Pav -IC4974 NED01,G,20:15:26.13,-61:51:27.6,Pav -IC4974 NED02,G,20:15:26.95,-61:51:47.0,Pav -IC4975,G,20:14:03.02,-52:43:18.4,Tel -IC4976,G,20:15:41.06,-61:52:30.5,Pav -IC4977,Other,20:11:53.20,-21:38:11.9,Cap -IC4978,G,20:14:37.65,-54:25:19.1,Tel -IC4979,G,20:14:41.84,-53:27:31.3,Tel -IC4980,G,20:15:28.93,-57:54:45.7,Pav -IC4981,G,20:19:39.20,-70:50:54.7,Pav -IC4982,G,20:20:20.84,-71:00:28.2,Pav -IC4983,G,20:16:05.95,-52:05:11.5,Tel -IC4984,G,20:16:17.45,-52:42:13.4,Tel -IC4985,G,20:20:44.02,-70:59:13.2,Pav -IC4986,G,20:17:11.65,-55:02:11.0,Tel -IC4987,G,20:17:19.38,-52:16:46.4,Tel -IC4988,Other,20:21:46.25,-69:23:15.5,Pav -IC4989,G,20:19:23.69,-58:33:05.3,Pav -IC4990,G,20:21:25.59,-66:53:27.1,Pav -IC4991,G,20:18:23.28,-41:03:00.6,Sgr -IC4992,G,20:23:27.85,-71:33:53.7,Pav -IC4993,G,20:21:56.36,-66:59:07.4,Pav -IC4994,G,20:19:44.52,-53:26:50.5,Tel -IC4995,G,20:19:58.97,-52:37:19.1,Tel -IC4996,OCl,20:16:33.27,+37:33:19.0,Cyg -IC4997,PN,20:20:08.80,+16:43:54.0,Sge -IC4998,G,20:22:10.58,-38:18:30.0,Sgr -IC4999,G,20:23:56.34,-26:00:53.8,Cap -IC5000,Dup,20:22:21.51,+06:25:47.5,Aql -IC5001,G,20:26:20.05,-54:46:29.0,Tel -IC5002,G,20:26:39.89,-54:47:58.9,Tel -IC5003,G,20:43:14.34,-29:51:12.2,Mic -IC5004,Dup,20:31:39.07,-30:49:54.8,Mic -IC5005,G,20:25:20.21,-25:49:45.4,Cap -IC5006,**,20:23:46.92,+06:26:57.0,Aql -IC5007,G,20:43:34.45,-29:42:13.1,Mic -IC5008,G,20:32:45.03,-72:41:41.0,Pav -IC5009,G,20:32:34.35,-72:10:03.2,Pav -IC5010,G,20:30:26.68,-66:05:50.5,Pav -IC5011,G,20:28:33.82,-36:01:37.6,Mic -IC5012,G,20:29:31.98,-56:44:35.2,Pav -IC5013,Dup,20:28:33.82,-36:01:37.6,Mic -IC5014,G,20:35:15.72,-73:27:09.4,Pav -IC5015,Dup,20:34:20.57,-31:58:51.2,Mic -IC5016,G,20:35:36.92,-72:54:40.1,Pav -IC5017,G,20:32:03.83,-57:35:15.7,Ind -IC5018,Dup,20:22:10.58,-38:18:30.0,Sgr -IC5019,G,20:30:47.09,-36:04:37.0,Mic -IC5020,G,20:30:38.49,-33:29:08.0,Mic -IC5021,G,20:33:34.04,-54:31:14.8,Ind -IC5022,G,20:41:06.14,-76:27:00.5,Oct -IC5023,G,20:38:10.72,-67:11:04.7,Pav -IC5024,G,20:40:09.54,-71:06:27.7,Pav -IC5025,G,20:44:59.08,-76:59:04.9,Oct -IC5026,G,20:48:28.05,-78:04:09.3,Oct -IC5027,G,20:41:08.93,-55:28:19.6,Ind -IC5028,G,20:43:22.03,-65:38:52.1,Pav -IC5029,Dup,20:43:14.34,-29:51:12.2,Mic -IC5030,Dup,20:43:34.45,-29:42:13.1,Mic -IC5031,G,20:45:20.27,-67:32:21.6,Pav -IC5032,G,20:45:22.03,-67:33:06.8,Pav -IC5033,G,20:43:55.09,-57:20:03.4,Ind -IC5034,G,20:43:41.67,-57:01:49.1,Ind -IC5035,G,20:44:14.45,-57:07:39.1,Ind -IC5036,G,20:44:37.78,-57:37:36.4,Ind -IC5037,G,20:45:39.25,-58:26:59.2,Ind -IC5038,G,20:46:51.65,-65:01:00.5,Pav -IC5039,Dup,20:43:14.34,-29:51:12.2,Mic -IC5040,G,20:52:19.61,-76:41:11.6,Oct -IC5041,Dup,20:43:34.45,-29:42:13.1,Mic -IC5042,G,20:47:46.02,-65:05:03.5,Pav -IC5043,G,20:46:38.26,-56:59:01.2,Ind -IC5044,G,20:50:41.54,-71:53:57.9,Pav -IC5045,G,20:50:50.11,-71:54:35.2,Pav -IC5046,Dup,20:43:14.34,-29:51:12.2,Mic -IC5047,Dup,20:43:34.45,-29:42:13.1,Mic -IC5048,G,20:51:40.64,-71:48:02.8,Pav -IC5049,GPair,20:47:23.44,-38:24:56.5,Mic -IC5049A,G,20:47:23.22,-38:24:47.4,Mic -IC5049B,G,20:47:23.83,-38:25:05.8,Mic -IC5050,G,20:45:15.05,-05:37:22.0,Aqr -IC5051,G,20:52:22.65,-71:47:23.6,Pav -IC5052,G,20:52:05.57,-69:12:05.9,Pav -IC5053,G,20:53:36.21,-71:08:28.4,Pav -IC5054,G,20:53:45.42,-71:01:29.1,Pav -IC5055,G,20:52:57.17,-68:26:44.4,Pav -IC5056,Other,20:48:59.50,-39:10:51.3,Mic -IC5057,*,20:47:13.50,+00:19:19.6,Aqr -IC5058,Dup,20:47:20.37,+00:29:02.6,Aqr -IC5059,G,20:51:13.48,-57:41:19.2,Ind -IC5060,G,20:54:46.66,-71:38:11.3,Pav -IC5061,Other,20:47:37.15,+00:20:08.4,Aqr -IC5062,**,20:48:10.20,-08:21:35.1,Aqr -IC5063,G,20:52:02.34,-57:04:07.6,Ind -IC5064,G,20:52:38.28,-57:13:56.8,Ind -IC5065,G,20:51:45.80,-29:50:50.2,Mic -IC5066,G,20:57:03.02,-73:08:50.1,Pav -IC5067,Other,20:47:50.18,+44:22:01.1,Cyg -IC5068,HII,20:50:29.76,+42:28:39.7,Cyg -IC5069,G,21:00:12.85,-71:48:42.1,Pav -IC5070,HII,20:51:00.72,+44:24:05.4,Cyg -IC5071,G,21:01:19.74,-72:38:33.8,Pav -IC5072,G,21:01:56.82,-72:59:18.5,Pav -IC5073,G,21:03:19.86,-72:41:16.4,Pav -IC5074,G,21:01:00.46,-63:09:09.6,Pav -IC5075,G,21:04:38.10,-71:52:04.3,Pav -IC5076,RfN,20:55:33.42,+47:23:44.0,Cyg -IC5077,G,21:08:53.96,-73:38:26.9,Pav -IC5078,G,21:02:31.27,-16:49:05.9,Cap -IC5079,*,21:05:44.01,-56:13:48.9,Ind -IC5080,G,21:02:33.07,+19:12:49.0,Del -IC5081,Other,21:03:01.23,+19:11:21.3,Del -IC5082,Dup,21:04:39.50,-12:20:18.2,Aqr -IC5083,G,21:03:51.50,+11:45:49.1,Equ -IC5084,G,21:09:14.87,-63:17:24.1,Pav -IC5085,G,21:13:27.57,-74:06:11.0,Pav -IC5086,G,21:08:32.01,-29:46:08.6,Mic -IC5087,G,21:14:21.45,-73:46:25.8,Pav -IC5088,G,21:09:26.76,-22:52:42.9,Cap -IC5089,G,21:10:54.53,-03:51:46.0,Aqr -IC5090,G,21:11:30.48,-02:01:57.2,Aqr -IC5091,G,21:17:37.02,-70:39:10.6,Pav -IC5092,G,21:16:14.50,-64:27:52.8,Pav -IC5093,G,21:18:46.38,-70:37:20.6,Pav -IC5094,G,21:17:49.50,-66:25:39.6,Pav -IC5095,G,21:17:22.24,-59:56:51.2,Pav -IC5096,G,21:18:21.54,-63:45:38.4,Pav -IC5097,Other,21:14:58.08,+04:29:02.8,Equ -IC5098,Other,21:15:01.38,+04:29:37.0,Equ -IC5099,G,21:21:49.05,-70:59:00.1,Pav -IC5100,G,21:21:43.46,-65:56:00.1,Pav -IC5101,G,21:21:55.77,-65:50:09.8,Pav -IC5102,G,21:26:13.46,-73:18:36.3,Pav -IC5103,G,21:29:12.55,-74:04:12.6,Pav -IC5104,G,21:21:29.25,+21:14:31.1,Peg -IC5105,G,21:24:22.01,-40:32:15.8,Mic -IC5106,G,21:28:38.13,-70:50:04.5,Pav -IC5107,G,21:28:14.89,-65:44:07.9,Pav -IC5108,G,21:32:51.50,-72:39:34.9,Ind -IC5109,G,21:33:42.71,-74:06:43.2,Ind -IC5110,G,21:30:43.39,-60:00:06.6,Ind -IC5111,G,21:28:10.76,+02:28:25.5,Aqr -IC5112,NonEx,,, -IC5113,**,21:29:39.49,+06:49:04.8,Peg -IC5114,Dup,21:34:07.94,-36:39:13.4,Gru -IC5115,G,21:30:57.29,+11:45:48.6,Peg -IC5116,G,21:37:05.50,-70:58:57.2,Ind -IC5117,PN,21:32:30.97,+44:35:47.6,Cyg -IC5118,G,21:42:13.80,-71:22:57.7,Ind -IC5119,G,21:33:55.85,+21:50:16.5,Peg -IC5120,Dup,21:38:48.27,-64:21:00.7,Ind -IC5121,Dup,21:41:19.28,-63:54:31.3,Ind -IC5122,G,21:39:45.87,-22:24:23.5,Cap -IC5123,G,21:44:49.35,-72:25:14.7,Ind -IC5124,G,21:39:55.19,-22:25:37.0,Cap -IC5125,G,21:41:50.18,-52:46:25.0,Ind -IC5126,G,21:40:28.57,-06:20:44.7,Aqr -IC5127,Dup,21:39:44.49,+06:17:10.7,Peg -IC5128,G,21:43:11.77,-38:58:05.8,Gru -IC5129,G,21:47:46.58,-65:23:16.1,Ind -IC5130,G,21:50:24.27,-73:59:50.7,Ind -IC5131,G,21:47:25.31,-34:53:01.2,PsA -IC5132,Neb,21:42:40.21,+66:10:06.4,Cep -IC5133,Neb,21:42:47.14,+66:10:52.1,Cep -IC5134,Neb,21:42:58.68,+66:06:10.2,Cep -IC5135,Dup,21:48:19.52,-34:57:04.5,PsA -IC5136,Dup,21:49:46.01,-34:52:34.6,PsA -IC5137,Other,21:51:37.46,-65:35:01.2,Ind -IC5138,G,21:53:21.69,-68:57:13.1,Ind -IC5139,G,21:50:25.66,-30:59:41.0,PsA -IC5140,G,21:54:16.03,-67:19:53.0,Ind -IC5141,G,21:53:17.04,-59:29:36.8,Ind -IC5142,G,21:55:20.49,-65:30:36.1,Ind -IC5143,Dup,21:56:09.73,-49:31:19.0,Ind -IC5144,G,21:54:09.53,+15:02:12.6,Peg -IC5145,G,21:54:23.06,+15:09:24.6,Peg -IC5146,Cl+N,21:53:28.76,+47:16:00.9,Cyg -IC5147,G,21:59:26.32,-65:26:59.5,Ind -IC5148,PN,21:59:35.20,-39:23:09.0,Gru -IC5149,G,21:58:59.04,-27:24:50.1,PsA -IC5150,Dup,21:59:35.20,-39:23:09.0,Gru -IC5151,G,21:58:52.60,+03:45:41.3,Peg -IC5152,G,22:02:41.51,-51:17:47.2,Ind -IC5153,G,22:00:23.56,+17:51:50.1,Peg -IC5154,G,22:04:30.29,-66:06:44.6,Ind -IC5155,**,22:02:06.26,+00:29:17.8,Aqr -IC5156,G,22:03:14.87,-33:50:18.4,PsA -IC5157,G,22:03:27.00,-34:56:30.6,PsA -IC5158,G,22:06:24.77,-67:31:01.2,Ind -IC5159,*,22:02:39.89,+00:19:08.4,Aqr -IC5160,G,22:03:04.84,+10:55:29.5,Peg -IC5161,G,22:05:39.01,+09:38:24.6,Peg -IC5162,G,22:08:03.08,-52:42:49.9,Ind -IC5163,Other,22:05:47.38,+27:05:07.8,Peg -IC5164,*,22:05:58.20,+27:02:50.9,Peg -IC5165,G,22:10:07.08,-64:34:40.9,Tuc -IC5166,*,22:06:06.84,+27:04:04.8,Peg -IC5167,*,22:07:31.79,-08:07:21.4,Aqr -IC5168,G,22:08:45.54,-27:51:23.3,PsA -IC5169,G,22:10:09.98,-36:05:19.0,PsA -IC5170,G,22:12:29.64,-47:13:19.0,Gru -IC5171,G,22:10:56.70,-46:04:53.3,Gru -IC5172,G,22:09:55.51,+12:49:05.0,Peg -IC5173,GPair,22:14:41.40,-69:21:59.0,Ind -IC5173A,G,22:14:44.57,-69:21:55.4,Ind -IC5173B,G,22:14:38.67,-69:22:03.9,Ind -IC5174,G,22:12:44.67,-38:10:17.0,Gru -IC5175,G,22:12:48.23,-38:07:38.8,Gru -IC5176,G,22:14:55.93,-66:50:57.9,Tuc -IC5177,G,22:11:34.27,+11:47:45.4,Peg -IC5178,G,22:12:33.35,-22:57:14.6,Aqr -IC5179,G,22:16:09.10,-36:50:37.4,Gru -IC5180,G,22:11:12.01,+38:55:37.9,Lac -IC5181,G,22:13:21.70,-46:01:03.4,Gru -IC5182,G,22:16:05.09,-65:27:17.0,Tuc -IC5183,Dup,22:16:09.10,-36:50:37.4,Gru -IC5184,Dup,22:16:09.10,-36:50:37.4,Gru -IC5185,G,22:17:43.65,-65:51:26.9,Tuc -IC5186,G,22:18:46.52,-36:48:05.7,Gru -IC5187,G,22:18:17.77,-59:36:25.1,Tuc -IC5188,G,22:18:26.40,-59:38:28.8,Tuc -IC5189,*,22:16:14.15,-05:00:15.5,Aqr -IC5190,G,22:19:00.69,-59:52:57.8,Tuc -IC5191,G,22:15:02.47,+37:18:01.4,Lac -IC5192,G,22:15:14.04,+37:16:15.7,Lac -IC5193,G,22:15:43.55,+37:14:34.2,Lac -IC5194,Other,22:17:08.13,-15:56:44.5,Aqr -IC5195,Dup,22:15:41.57,+37:18:10.0,Lac -IC5196,G,22:20:11.26,-65:24:16.6,Tuc -IC5197,G,22:19:49.42,-60:08:11.2,Tuc -IC5198,Dup,22:17:42.68,-15:34:16.6,Aqr -IC5199,G,22:19:33.22,-37:32:01.7,Gru -IC5200,G,22:22:15.34,-65:45:58.8,Tuc -IC5201,G,22:20:57.44,-46:02:09.1,Gru -IC5202,G,22:22:55.67,-65:48:10.4,Tuc -IC5203,G,22:22:34.30,-59:46:23.6,Tuc -IC5204,Dup,22:30:59.91,-14:00:12.7,Aqr -IC5205,G,22:22:47.56,-59:47:13.4,Tuc -IC5206,G,22:24:04.62,-66:51:28.2,Tuc -IC5207,G,22:23:29.45,-60:33:53.9,Tuc -IC5208,G,22:24:34.27,-65:13:38.7,Tuc -IC5209,G,22:23:09.10,-37:59:35.8,Gru -IC5210,G,22:22:31.11,-18:52:10.9,Aqr -IC5211,G,22:22:43.06,-18:52:48.8,Aqr -IC5212,G,22:23:30.26,-38:02:15.5,Gru -IC5213,G,22:25:04.79,-60:28:36.1,Tuc -IC5214,Other,22:23:42.21,-27:28:11.3,PsA -IC5215,G,22:26:58.11,-65:58:55.9,Tuc -IC5216,Other,22:24:44.77,-18:05:15.4,Aqr -IC5217,PN,22:23:55.70,+50:58:00.0,Lac -IC5218,G,22:28:05.78,-60:23:41.5,Tuc -IC5219,G,22:28:43.74,-65:53:37.4,Tuc -IC5220,G,22:28:02.73,-59:43:24.4,Tuc -IC5221,G,22:28:57.71,-65:54:16.5,Tuc -IC5222,G,22:29:54.78,-65:39:40.9,Tuc -IC5223,G,22:29:44.77,+07:59:19.5,Peg -IC5224,G,22:30:30.15,-45:59:46.3,Gru -IC5225,Dup,22:32:08.06,-25:23:52.0,PsA -IC5226,G,22:32:30.21,-25:39:43.1,PsA -IC5227,G,22:34:03.71,-64:41:51.6,Tuc -IC5228,Dup,22:32:23.80,-14:07:13.9,Aqr -IC5229,G,22:34:50.38,-61:22:52.9,Tuc -IC5230,G,22:35:40.17,-61:32:51.7,Tuc -IC5231,G,22:34:00.72,+23:20:19.2,Peg -IC5232,G,22:37:38.11,-68:52:20.0,Ind -IC5233,G,22:36:32.88,+25:45:47.5,Peg -IC5234,G,22:40:11.45,-65:49:30.9,Tuc -IC5235,G,22:41:25.47,-66:34:49.1,Tuc -IC5236,G,22:41:30.14,-66:37:04.8,Tuc -IC5237,Dup,22:42:17.91,-30:03:27.6,PsA -IC5238,G,22:41:29.77,-60:45:28.5,Tuc -IC5239,G,22:31:07.18,-38:01:35.5,Gru -IC5240,G,22:41:52.38,-44:46:01.8,Gru -IC5241,G,22:41:38.53,+02:38:23.8,Aqr -IC5242,G,22:41:15.21,+23:24:24.9,Peg -IC5243,G,22:41:24.59,+23:22:28.7,Peg -IC5244,G,22:44:13.75,-64:02:36.2,Tuc -IC5245,G,22:44:56.37,-65:21:28.5,Tuc -IC5246,G,22:46:39.50,-64:53:52.7,Tuc -IC5247,G,22:46:50.15,-65:16:26.0,Tuc -IC5248,Other,22:44:42.75,-00:20:22.2,Aqr -IC5249,G,22:47:06.28,-64:49:55.3,Tuc -IC5250,GPair,22:47:20.42,-65:03:31.4,Tuc -IC5250A,G,22:47:17.50,-65:03:35.0,Tuc -IC5250B,G,22:47:22.16,-65:03:30.6,Tuc -IC5251,Other,22:45:10.63,+11:09:30.5,Peg -IC5252,G,22:48:08.99,-68:54:10.3,Ind -IC5253,G,22:45:28.92,+21:48:29.0,Peg -IC5254,G,22:46:00.51,+21:07:32.1,Peg -IC5255,Other,22:45:46.04,+36:13:36.4,Lac -IC5256,G,22:49:45.81,-68:41:26.4,Ind -IC5257,G,22:52:16.25,-67:25:10.4,Ind -IC5258,G,22:51:31.57,+23:04:50.1,Peg -IC5259,G,22:55:14.69,+36:40:18.4,Lac -IC5260,Dup,22:54:18.61,-39:18:53.8,Gru -IC5261,G,22:54:25.24,-20:21:46.4,Aqr -IC5262,G,22:55:20.45,-33:53:16.5,PsA -IC5263,G,22:58:13.57,-69:03:07.6,Ind -IC5264,G,22:56:53.04,-36:33:15.0,Gru -IC5265,Dup,22:57:10.61,-36:27:44.0,Gru -IC5266,G,22:58:20.83,-65:07:46.9,Tuc -IC5267,G,22:57:13.57,-43:23:46.1,Gru -IC5268,Other,22:56:12.97,+36:35:50.1,Lac -IC5269,G,22:57:43.66,-36:01:34.4,PsA -IC5270,G,22:57:54.94,-35:51:29.0,PsA -IC5271,G,22:58:01.82,-33:44:32.0,PsA -IC5272,G,22:59:31.10,-65:11:36.7,Tuc -IC5273,G,22:59:26.70,-37:42:10.4,Gru -IC5274,G,22:58:27.65,+18:55:07.5,Peg -IC5275,Other,22:58:39.46,+18:51:45.1,Peg -IC5276,G,22:58:39.75,+18:49:11.7,Peg -IC5277,G,23:01:59.25,-65:11:52.4,Tuc -IC5278,G,23:00:15.84,-08:10:43.6,Aqr -IC5279,G,23:03:02.62,-69:12:35.0,Ind -IC5280,G,23:03:50.15,-65:12:28.3,Tuc -IC5281,*,23:02:23.49,+27:00:23.0,Peg -IC5282,G,23:02:48.19,+21:52:27.7,Peg -IC5283,G,23:03:18.00,+08:53:37.0,Peg -IC5284,G,23:06:46.27,+19:07:19.5,Peg -IC5285,G,23:06:58.94,+22:56:11.3,Peg -IC5286,G,23:09:55.51,-68:15:11.2,Ind -IC5287,G,23:09:20.27,+00:45:23.4,Psc -IC5288,G,23:11:44.22,-68:05:38.6,Ind -IC5289,GPair,23:11:16.96,-32:27:15.4,Scl -IC5289 NED01,G,23:11:17.20,-32:27:21.5,Scl -IC5289 NED02,G,23:11:17.30,-32:27:06.7,Scl -IC5290,G,23:12:53.26,-23:28:09.1,Aqr -IC5291,G,23:13:39.58,+09:14:29.5,Peg -IC5292,G,23:13:47.07,+13:41:15.4,Peg -IC5293,G,23:14:44.64,+25:08:26.2,Peg -IC5294,Dup,23:16:10.76,-42:35:05.1,Gru -IC5295,G,23:15:29.14,+25:07:13.7,Peg -IC5296,G,23:15:43.78,+25:05:40.3,Peg -IC5297,G,23:15:58.44,+25:01:30.7,Peg -IC5298,G,23:16:00.70,+25:33:24.1,Peg -IC5299,G,23:16:19.15,+20:51:21.0,Peg -IC5300,G,23:16:34.09,+20:49:42.4,Peg -IC5301,G,23:18:59.43,-69:33:43.6,Ind -IC5302,G,23:19:36.69,-64:34:05.8,Tuc -IC5303,**,23:17:54.74,+00:15:52.3,Psc -IC5304,G,23:18:52.55,-10:15:33.5,Aqr -IC5305,G,23:18:06.22,+10:17:59.5,Peg -IC5306,G,23:18:11.33,+10:14:45.8,Peg -IC5307,G,23:18:22.03,+10:14:08.7,Peg -IC5308,Dup,23:19:21.14,-42:15:24.6,Gru -IC5309,G,23:19:11.65,+08:06:33.5,Psc -IC5310,G,23:20:47.64,-22:08:57.7,Aqr -IC5311,**,23:20:37.75,+17:16:26.9,Peg -IC5312,G,23:20:58.32,+19:19:04.9,Peg -IC5313,Dup,23:22:00.90,-42:28:49.8,Gru -IC5314,G,23:21:08.54,+19:18:40.4,Peg -IC5315,G,23:21:18.25,+25:23:06.7,Peg -IC5316,G,23:21:54.11,+21:12:09.2,Peg -IC5317,G,23:23:28.68,+21:09:48.7,Peg -IC5318,Dup,23:24:06.96,-11:51:38.5,Aqr -IC5319,G,23:24:48.96,+13:59:47.5,Peg -IC5320,G,23:28:21.97,-67:45:37.0,Tuc -IC5321,G,23:26:20.23,-17:57:23.0,Aqr -IC5322,G,23:28:30.81,-67:45:40.8,Tuc -IC5323,G,23:27:36.97,-67:48:55.7,Tuc -IC5324,G,23:28:17.75,-67:49:16.9,Tuc -IC5325,G,23:28:43.43,-41:20:00.5,Phe -IC5326,G,23:29:35.16,-28:49:52.2,Scl -IC5327,Dup,23:30:47.74,-13:29:07.6,Aqr -IC5328,G,23:33:16.46,-45:00:57.5,Phe -IC5329,G,23:33:09.59,+21:14:14.3,Peg -IC5330,*,23:33:26.33,-02:52:59.3,Psc -IC5331,G,23:33:24.82,+21:07:48.2,Peg -IC5332,G,23:34:27.49,-36:06:03.9,Scl -IC5333,Dup,23:34:52.98,-65:23:45.7,Tuc -IC5334,G,23:34:36.42,-04:32:03.3,Aqr -IC5335,G,23:35:47.37,-67:23:49.1,Tuc -IC5336,GPair,23:36:19.69,+21:05:53.2,Peg -IC5336 NED01,G,23:36:18.72,+21:05:58.1,Peg -IC5336 NED02,G,23:36:20.44,+21:05:48.2,Peg -IC5337,G,23:36:25.03,+21:09:02.0,Peg -IC5338,G,23:36:30.43,+21:08:45.6,Peg -IC5339,G,23:38:05.27,-68:26:31.4,Tuc -IC5340,Other,23:38:32.35,-04:51:16.6,Aqr -IC5341,G,23:38:26.82,+26:59:06.4,Peg -IC5342,G,23:38:38.80,+27:00:40.9,Peg -IC5343,G,23:39:22.37,-22:29:49.8,Aqr -IC5344,Other,23:39:15.84,-04:58:00.3,Aqr -IC5345,G,23:39:32.23,-22:24:48.0,Aqr -IC5346,G,23:41:06.33,+24:56:59.3,Peg -IC5347,G,23:41:36.71,+24:53:08.8,Peg -IC5348,Dup,23:44:59.24,-42:54:39.3,Phe -IC5349,GPair,23:46:22.83,-28:00:18.4,Scl -IC5349 NED01,G,23:46:22.70,-28:00:21.7,Scl -IC5349 NED02,G,23:46:23.09,-28:00:11.7,Scl -IC5350,G,23:47:14.63,-27:57:27.8,Scl -IC5351,G,23:47:18.93,-02:18:48.6,Psc -IC5352,G,23:47:19.90,-02:16:50.4,Psc -IC5353,G,23:47:28.59,-28:06:34.1,Scl -IC5354,G,23:47:28.34,-28:08:09.6,Scl -IC5355,G,23:47:15.29,+32:46:57.9,And -IC5356,G,23:47:23.79,-02:21:04.5,Psc -IC5357,G,23:47:22.99,-02:18:02.4,Psc -IC5358,G,23:47:45.04,-28:08:26.7,Scl -IC5359,G,23:47:37.86,-02:19:00.0,Psc -IC5360,Other,23:47:53.73,-37:03:31.2,Scl -IC5361,Dup,23:51:28.89,-13:22:53.7,Aqr -IC5362,G,23:51:36.73,-28:21:54.2,Scl -IC5363,Dup,23:51:36.73,-28:21:54.2,Scl -IC5364,GPair,23:56:25.00,-29:01:24.0,Scl -IC5364 NED01,G,23:56:23.91,-29:01:25.3,Scl -IC5364 NED02,G,23:56:25.24,-29:01:24.2,Scl -IC5365,Other,23:57:34.59,-37:01:29.6,Scl -IC5366,Other,23:57:40.39,+52:47:29.9,Cas -IC5367,G,23:58:38.91,+22:26:56.7,Peg -IC5368,Dup,23:59:06.60,+06:52:23.0,Psc -IC5369,G,23:59:50.54,+32:42:08.5,And -IC5370,G,00:00:09.18,+32:44:18.2,And -IC5371,G,00:00:14.78,+32:49:55.2,And -IC5372,G,00:00:16.26,+32:47:33.4,And -IC5373,G,00:00:28.81,+32:46:56.5,And -IC5374,G,00:01:04.52,+04:30:00.7,Psc -IC5375,G,00:01:04.77,+04:32:26.2,Psc -IC5376,G,00:01:19.77,+34:31:32.6,And -IC5377,G,00:02:05.40,+16:35:25.0,Peg -IC5378,GPair,00:02:37.80,+16:38:53.0,Peg -IC5378 NED01,G,00:02:37.90,+16:38:37.6,Peg -IC5378 NED02,G,00:02:37.70,+16:39:08.0,Peg -IC5379,G,00:02:40.70,+16:36:01.0,Peg -IC5380,G,00:02:49.56,-66:11:11.5,Tuc -IC5381,G,00:03:11.27,+15:57:56.6,Peg -IC5382,G,00:03:26.20,-65:11:48.0,Tuc -IC5383,Other,00:03:48.80,+16:00:51.0,Peg -IC5384,Dup,00:04:09.12,-11:59:02.0,Cet -IC5385,Other,00:06:23.70,-00:04:36.0,Psc -IC5386,Dup,00:06:28.46,-03:42:58.1,Psc -NGC0001,G,00:07:15.84,+27:42:29.1,Peg -NGC0002,G,00:07:17.11,+27:40:42.1,Peg -NGC0003,G,00:07:16.80,+08:18:05.9,Psc -NGC0004,G,00:07:24.41,+08:22:25.6,Psc -NGC0005,G,00:07:48.87,+35:21:44.3,And -NGC0006,G,00:09:32.70,+33:18:31.2,And -NGC0007,G,00:08:20.96,-29:54:54.0,Scl -NGC0008,**,00:08:45.30,+23:50:20.0,Peg -NGC0009,G,00:08:54.70,+23:49:01.1,Peg -NGC0010,G,00:08:34.54,-33:51:30.0,Scl -NGC0011,G,00:08:42.50,+37:26:52.5,And -NGC0012,G,00:08:44.75,+04:36:45.1,Psc -NGC0013,G,00:08:47.72,+33:26:00.0,And -NGC0014,G,00:08:46.40,+15:48:56.0,Peg -NGC0015,G,00:09:02.47,+21:37:28.3,Peg -NGC0016,G,00:09:04.29,+27:43:45.9,Peg -NGC0017,G,00:11:06.55,-12:06:26.3,Cet -NGC0018,**,00:09:23.10,+27:43:55.5,Peg -NGC0019,G,00:10:40.87,+32:58:59.1,And -NGC0020,Dup,00:09:32.70,+33:18:31.2,And -NGC0021,G,00:10:46.90,+33:21:10.2,And -NGC0022,G,00:09:48.19,+27:49:56.3,Peg -NGC0023,G,00:09:53.41,+25:55:25.6,Peg -NGC0024,G,00:09:56.54,-24:57:47.3,Scl -NGC0025,G,00:09:59.28,-57:01:15.0,Phe -NGC0026,G,00:10:25.87,+25:49:54.6,Peg -NGC0027,G,00:10:32.77,+28:59:46.5,And -NGC0028,G,00:10:25.24,-56:59:20.9,Phe -NGC0029,Dup,00:10:46.90,+33:21:10.2,And -NGC0030,**,00:10:50.79,+21:58:37.1,Peg -NGC0031,G,00:10:38.39,-56:59:11.4,Phe -NGC0032,*,00:10:53.59,+18:47:45.6,Peg -NGC0033,**,00:10:56.64,+03:40:33.3,Psc -NGC0034,Dup,00:11:06.55,-12:06:26.3,Cet -NGC0035,G,00:11:10.48,-12:01:15.3,Cet -NGC0036,G,00:11:22.30,+06:23:21.7,Psc -NGC0037,G,00:11:22.93,-56:57:26.4,Phe -NGC0038,G,00:11:46.99,-05:35:10.6,Psc -NGC0039,G,00:12:18.86,+31:03:39.9,And -NGC0040,PN,00:13:01.03,+72:31:19.0,Cep -NGC0041,G,00:12:47.97,+22:01:24.2,Peg -NGC0042,G,00:12:56.35,+22:06:01.1,Peg -NGC0043,G,00:13:00.75,+30:54:55.0,And -NGC0044,**,00:13:13.40,+31:17:10.5,And -NGC0045,G,00:14:03.99,-23:10:55.5,Cet -NGC0046,*,00:14:09.86,+05:59:15.7,Psc -NGC0047,G,00:14:30.64,-07:10:02.8,Cet -NGC0048,G,00:14:02.19,+48:14:05.5,And -NGC0049,G,00:14:22.43,+48:14:47.8,And -NGC0050,G,00:14:44.57,-07:20:42.3,Cet -NGC0051,G,00:14:34.92,+48:15:20.5,And -NGC0052,G,00:14:40.11,+18:34:55.3,Peg -NGC0053,G,00:14:42.85,-60:19:43.5,Tuc -NGC0054,G,00:15:07.67,-07:06:24.2,Cet -NGC0055,G,00:14:53.60,-39:11:47.9,Scl -NGC0056,Other,00:15:20.66,+12:26:40.3,Psc -NGC0057,G,00:15:30.87,+17:19:42.7,Psc -NGC0058,Dup,00:14:30.64,-07:10:02.8,Cet -NGC0059,G,00:15:25.13,-21:26:39.8,Cet -NGC0060,G,00:15:58.24,-00:18:12.7,Psc -NGC0061,GPair,00:16:24.20,-06:19:10.0,Cet -NGC0061A,G,00:16:24.34,-06:19:18.9,Cet -NGC0061B,G,00:16:24.07,-06:19:07.9,Cet -NGC0062,G,00:17:05.42,-13:29:13.6,Cet -NGC0063,G,00:17:45.52,+11:27:01.2,Psc -NGC0064,G,00:17:30.37,-06:49:28.6,Cet -NGC0065,G,00:18:58.71,-22:52:49.3,Cet -NGC0066,G,00:19:04.94,-22:56:11.0,Cet -NGC0067,G,00:18:12.19,+30:03:19.9,And -NGC0068,G,00:18:18.49,+30:04:18.3,And -NGC0069,G,00:18:20.51,+30:02:23.9,And -NGC0070,G,00:18:22.54,+30:04:46.5,And -NGC0071,G,00:18:23.58,+30:03:47.7,And -NGC0072,G,00:18:28.37,+30:02:26.5,And -NGC0072A,G,00:18:34.3,+30:02:11,And -NGC0073,G,00:18:39.01,-15:19:20.1,Cet -NGC0074,G,00:18:49.34,+30:03:43.1,And -NGC0075,G,00:19:26.33,+06:26:57.7,Psc -NGC0076,G,00:19:37.79,+29:56:01.9,And -NGC0077,G,00:20:01.65,-22:31:55.6,Cet -NGC0078,GPair,00:20:26.60,+00:49:47.0,Psc -NGC0078A,G,00:20:25.79,+00:49:34.8,Psc -NGC0078B,G,00:20:27.51,+00:50:00.8,Psc -NGC0079,G,00:21:02.85,+22:33:59.7,And -NGC0080,G,00:21:10.85,+22:21:25.9,And -NGC0081,G,00:21:13.27,+22:22:58.4,And -NGC0082,*,00:21:17.55,+22:27:37.3,And -NGC0083,G,00:21:22.40,+22:26:01.0,And -NGC0084,*,00:21:21.25,+22:37:10.9,And -NGC0085,G,00:21:25.53,+22:30:42.4,And -NGC0085B,G,00:21:29.0,+22:30:21,And -NGC0086,G,00:21:28.57,+22:33:23.1,And -NGC0087,G,00:21:14.27,-48:37:41.6,Phe -NGC0088,G,00:21:22.12,-48:38:24.6,Phe -NGC0089,G,00:21:24.36,-48:39:55.1,Phe -NGC0090,G,00:21:51.40,+22:24:00.0,And -NGC0091,*,00:21:51.72,+22:22:06.1,And -NGC0092,G,00:21:31.70,-48:37:29.1,Phe -NGC0093,G,00:22:03.23,+22:24:29.1,And -NGC0094,G,00:22:13.53,+22:28:59.0,And -NGC0095,G,00:22:13.54,+10:29:29.7,Psc -NGC0096,G,00:22:17.71,+22:32:46.5,And -NGC0097,G,00:22:30.00,+29:44:43.3,And -NGC0098,G,00:22:49.54,-45:16:08.4,Phe -NGC0099,G,00:23:59.39,+15:46:13.5,Psc -NGC0100,G,00:24:02.84,+16:29:11.0,Psc -NGC0101,G,00:23:54.61,-32:32:10.2,Scl -NGC0102,G,00:24:36.53,-13:57:22.9,Cet -NGC0103,OCl,00:25:16.40,+61:19:24.5,Cas -NGC0104,GCl,00:24:05.36,-72:04:53.2,Tuc -NGC0105,G,00:25:16.78,+12:53:01.9,Psc -NGC0106,G,00:24:43.75,-05:08:55.5,Psc -NGC0107,G,00:25:42.20,-08:16:58.1,Cet -NGC0108,G,00:25:59.73,+29:12:43.4,And -NGC0109,G,00:26:14.64,+21:48:26.3,And -NGC0110,OCl,00:27:25.01,+71:23:29.5,Cas -NGC0111,Other,00:26:38.41,-02:37:29.9,Cet -NGC0112,G,00:26:48.73,+31:42:11.9,And -NGC0113,G,00:26:54.64,-02:30:03.2,Cet -NGC0114,G,00:26:58.23,-01:47:10.5,Cet -NGC0115,G,00:26:46.28,-33:40:37.5,Scl -NGC0116,G,00:27:05.23,-07:40:05.7,Cet -NGC0117,G,00:27:11.07,+01:20:01.4,Cet -NGC0118,G,00:27:16.22,-01:46:48.5,Cet -NGC0119,G,00:26:57.61,-56:58:41.0,Phe -NGC0120,G,00:27:30.08,-01:30:48.5,Cet -NGC0121,GCl,00:26:48.25,-71:32:08.4,Tuc -NGC0122,*,00:27:38.33,-01:38:25.7,Cet -NGC0123,*,00:27:39.99,-01:37:39.5,Cet -NGC0124,G,00:27:52.36,-01:48:36.7,Cet -NGC0125,G,00:28:50.19,+02:50:19.9,Psc -NGC0126,G,00:29:08.10,+02:48:40.0,Psc -NGC0127,G,00:29:12.37,+02:52:21.7,Psc -NGC0128,G,00:29:15.06,+02:51:50.6,Psc -NGC0129,OCl,00:29:58.19,+60:12:40.2,Cas -NGC0130,G,00:29:18.55,+02:52:13.6,Psc -NGC0131,G,00:29:38.52,-33:15:35.1,Scl -NGC0132,G,00:30:10.71,+02:05:36.4,Cet -NGC0133,OCl,00:31:16.96,+63:21:09.4,Cas -NGC0134,G,00:30:21.97,-33:14:38.5,Scl -NGC0135,G,00:31:45.94,-13:20:14.9,Cet -NGC0136,OCl,00:31:30.79,+61:30:33.3,Cas -NGC0137,G,00:30:58.10,+10:12:30.3,Psc -NGC0138,G,00:30:59.24,+05:09:35.1,Psc -NGC0139,G,00:31:06.40,+05:04:43.5,Psc -NGC0140,G,00:31:20.47,+30:47:33.0,And -NGC0141,G,00:31:17.44,+05:10:47.1,Psc -NGC0142,G,00:31:08.08,-22:37:07.2,Cet -NGC0143,G,00:31:15.62,-22:33:35.8,Cet -NGC0144,G,00:31:20.67,-22:38:44.6,Cet -NGC0145,G,00:31:45.74,-05:09:09.5,Cet -NGC0146,OCl,00:33:03.94,+63:18:32.4,Cas -NGC0147,G,00:33:12.12,+48:30:31.5,Cas -NGC0148,G,00:34:15.50,-31:47:09.6,Scl -NGC0149,G,00:33:50.26,+30:43:24.3,And -NGC0150,G,00:34:15.48,-27:48:12.9,Scl -NGC0151,G,00:34:02.79,-09:42:19.2,Cet -NGC0152,OCl,00:32:52.46,-73:07:13.4,Tuc -NGC0153,Dup,00:34:02.79,-09:42:19.2,Cet -NGC0154,G,00:34:19.47,-12:39:22.7,Cet -NGC0155,G,00:34:40.08,-10:45:59.4,Cet -NGC0156,**,00:34:35.82,-08:20:24.2,Cet -NGC0157,G,00:34:46.76,-08:23:47.2,Cet -NGC0158,**,00:35:05.60,-08:20:44.6,Cet -NGC0159,G,00:34:35.53,-55:47:23.9,Phe -NGC0160,G,00:36:04.06,+23:57:28.4,And -NGC0161,G,00:35:33.94,-02:50:55.3,Cet -NGC0162,*,00:36:09.26,+23:57:44.7,And -NGC0163,G,00:35:59.83,-10:07:18.1,Cet -NGC0164,G,00:36:32.91,+02:44:59.2,Psc -NGC0165,G,00:36:28.92,-10:06:22.2,Cet -NGC0166,G,00:35:48.78,-13:36:38.3,Cet -NGC0167,G,00:35:23.09,-23:22:30.0,Cet -NGC0168,G,00:36:38.66,-22:35:36.6,Cet -NGC0169,G,00:36:51.60,+23:59:27.3,And -NGC0169A,G,00:36:52.33,+23:59:05.9,And -NGC0170,G,00:36:45.82,+01:53:11.3,Cet -NGC0171,G,00:37:21.53,-19:56:03.3,Cet -NGC0172,G,00:37:13.61,-22:35:13.3,Cet -NGC0173,G,00:37:12.47,+01:56:32.1,Cet -NGC0174,G,00:36:58.94,-29:28:40.2,Scl -NGC0175,Dup,00:37:21.53,-19:56:03.3,Cet -NGC0176,OCl,00:35:57.87,-73:09:59.0,Tuc -NGC0177,G,00:37:34.34,-22:32:57.3,Cet -NGC0178,G,00:39:08.40,-14:10:22.2,Cet -NGC0179,G,00:37:46.28,-17:50:58.1,Cet -NGC0180,G,00:37:57.70,+08:38:06.7,Psc -NGC0181,G,00:38:23.21,+29:28:21.3,And -NGC0182,G,00:38:12.38,+02:43:42.8,Psc -NGC0183,G,00:38:29.39,+29:30:40.4,And -NGC0184,G,00:38:35.78,+29:26:51.4,And -NGC0185,G,00:38:57.97,+48:20:14.6,Cas -NGC0186,G,00:38:25.30,+03:09:59.2,Psc -NGC0187,G,00:39:30.41,-14:39:22.7,Cet -NGC0188,OCl,00:47:27.53,+85:16:10.7,Cep -NGC0189,OCl,00:39:35.70,+61:05:40.1,Cas -NGC0190,GPair,00:38:54.70,+07:03:35.0,Psc -NGC0190 NED01,G,00:38:54.68,+07:03:45.7,Psc -NGC0190 NED02,G,00:38:54.73,+07:03:24.2,Psc -NGC0191,G,00:38:59.44,-09:00:09.4,Cet -NGC0191A,G,00:39:00.24,-09:00:52.5,Cet -NGC0192,G,00:39:13.43,+00:51:51.6,Cet -NGC0193,G,00:39:18.59,+03:19:52.0,Psc -NGC0194,G,00:39:18.42,+03:02:14.8,Psc -NGC0195,G,00:39:35.79,-09:11:40.3,Cet -NGC0196,G,00:39:17.84,+00:54:45.9,Cet -NGC0197,G,00:39:18.79,+00:53:30.9,Cet -NGC0198,G,00:39:22.98,+02:47:52.5,Psc -NGC0199,G,00:39:33.17,+03:08:18.7,Psc -NGC0200,G,00:39:34.88,+02:53:14.8,Psc -NGC0201,G,00:39:34.82,+00:51:35.6,Cet -NGC0202,G,00:39:39.85,+03:32:10.6,Psc -NGC0203,G,00:39:39.53,+03:26:34.4,Psc -NGC0204,G,00:39:44.27,+03:17:58.5,Psc -NGC0205,G,00:40:22.08,+41:41:07.1,And -NGC0206,*Ass,00:40:31.30,+40:44:21.4,And -NGC0207,G,00:39:40.71,-14:14:13.5,Cet -NGC0208,G,00:40:17.58,+02:45:23.4,Psc -NGC0209,G,00:39:03.60,-18:36:29.8,Cet -NGC0210,G,00:40:35.02,-13:52:22.1,Cet -NGC0211,Dup,00:39:39.53,+03:26:34.4,Psc -NGC0212,G,00:40:13.33,-56:09:10.9,Phe -NGC0213,G,00:41:10.00,+16:28:09.8,Psc -NGC0214,G,00:41:28.03,+25:29:58.0,And -NGC0215,G,00:40:48.87,-56:12:50.6,Phe -NGC0216,G,00:41:27.15,-21:02:43.6,Cet -NGC0217,G,00:41:33.89,-10:01:17.1,Cet -NGC0218,G,00:46:31.99,+36:19:32.2,And -NGC0219,G,00:42:11.31,+00:54:16.4,Cet -NGC0220,OCl,00:40:29.89,-73:24:14.3,Tuc -NGC0221,G,00:42:41.83,+40:51:55.0,And -NGC0222,OCl,00:40:43.71,-73:23:08.5,Tuc -NGC0223,G,00:42:15.88,+00:50:43.8,Cet -NGC0224,G,00:42:44.35,+41:16:08.6,And -NGC0225,OCl,00:43:36.38,+61:46:01.0,Cas -NGC0226,G,00:42:54.04,+32:34:51.5,And -NGC0227,G,00:42:36.83,-01:31:43.6,Cet -NGC0228,G,00:42:54.51,+23:30:11.0,And -NGC0229,G,00:43:04.64,+23:30:32.8,And -NGC0230,G,00:42:27.17,-23:37:43.7,Cet -NGC0231,OCl,00:41:06.43,-73:21:08.7,Tuc -NGC0232,G,00:42:45.82,-23:33:40.9,Cet -NGC0233,G,00:43:36.56,+30:35:13.2,And -NGC0234,G,00:43:32.39,+14:20:33.2,Psc -NGC0235,GPair,00:42:53.20,-23:32:36.0,Cet -NGC0235A,G,00:42:52.81,-23:32:27.7,Cet -NGC0235B,G,00:42:53.60,-23:32:44.0,Cet -NGC0236,G,00:43:27.54,+02:57:29.5,Psc -NGC0237,G,00:43:27.84,-00:07:29.7,Cet -NGC0238,G,00:43:25.75,-50:10:58.2,Phe -NGC0239,G,00:44:37.51,-03:45:33.3,Cet -NGC0240,G,00:45:01.93,+06:06:48.1,Psc -NGC0241,OCl,00:43:31.53,-73:26:25.7,Tuc -NGC0242,OCl,00:43:33.78,-73:26:46.7,Tuc -NGC0243,G,00:46:00.87,+29:57:34.2,And -NGC0244,G,00:45:46.43,-15:35:48.8,Cet -NGC0245,G,00:46:05.39,-01:43:24.2,Cet -NGC0246,PN,00:47:03.36,-11:52:19.0,Cet -NGC0247,G,00:47:08.55,-20:45:37.4,Cet -NGC0247A,G,00:47:28.14,-20:23:51.5,Cet -NGC0247B,G,00:47:35.15,-20:25:43.3,Cet -NGC0247C,GPair,00:47:37.71,-20:27:10.1,Cet -NGC0247D,G,00:47:36.99,-20:29:09.6,Cet -NGC0248,Cl+N,00:45:24.04,-73:22:47.3,Tuc -NGC0249,HII,00:45:32.86,-73:04:48.3,Tuc -NGC0250,G,00:47:16.01,+07:54:36.1,Psc -NGC0251,G,00:47:54.03,+19:35:48.6,Psc -NGC0252,G,00:48:01.50,+27:37:25.1,And -NGC0253,G,00:47:33.12,-25:17:17.6,Scl -NGC0254,G,00:47:27.61,-31:25:18.3,Scl -NGC0255,G,00:47:47.31,-11:28:07.3,Cet -NGC0256,OCl,00:45:53.33,-73:30:24.6,Tuc -NGC0257,G,00:48:01.51,+08:17:49.5,Psc -NGC0258,G,00:48:12.79,+27:39:25.9,And -NGC0259,G,00:48:03.29,-02:46:31.2,Cet -NGC0260,G,00:48:34.65,+27:41:32.8,And -NGC0261,Neb,00:46:27.91,-73:06:13.1,Tuc -NGC0262,G,00:48:47.14,+31:57:25.1,And -NGC0263,G,00:48:48.47,-13:06:26.6,Cet -NGC0264,G,00:48:20.94,-38:14:03.7,Scl -NGC0265,OCl,00:47:10.16,-73:28:37.8,Tuc -NGC0266,G,00:49:47.80,+32:16:39.8,Psc -NGC0267,OCl,00:48:02.90,-73:16:26.5,Tuc -NGC0268,G,00:50:09.56,-05:11:37.4,Cet -NGC0269,OCl,00:48:22.03,-73:31:53.5,Tuc -NGC0270,G,00:50:32.52,-08:39:05.9,Cet -NGC0271,G,00:50:41.86,-01:54:36.9,Cet -NGC0272,OCl,00:51:25.16,+35:49:18.3,And -NGC0273,G,00:50:48.45,-06:53:08.5,Cet -NGC0274,G,00:51:01.86,-07:03:25.0,Cet -NGC0275,G,00:51:04.20,-07:04:00.0,Cet -NGC0276,G,00:52:06.57,-22:40:48.5,Cet -NGC0277,G,00:51:17.23,-08:35:48.6,Cet -NGC0278,G,00:52:04.31,+47:33:01.8,Cas -NGC0279,G,00:52:08.95,-02:13:06.4,Cet -NGC0280,G,00:52:30.26,+24:21:02.1,And -NGC0281,HII,00:52:59.35,+56:37:18.8,Cas -NGC0282,G,00:52:42.15,+30:38:20.6,Psc -NGC0283,G,00:53:13.21,-13:09:50.0,Cet -NGC0284,G,00:53:24.27,-13:09:31.9,Cet -NGC0285,G,00:53:29.89,-13:09:38.6,Cet -NGC0286,G,00:53:30.38,-13:06:46.0,Cet -NGC0287,G,00:53:28.29,+32:28:56.3,Psc -NGC0288,GCl,00:52:47.45,-26:35:23.6,Scl -NGC0289,G,00:52:42.36,-31:12:21.0,Scl -NGC0290,OCl,00:51:14.18,-73:09:41.5,Tuc -NGC0291,G,00:53:29.92,-08:46:04.1,Cet -NGC0292,G,00:52:44.78,-72:49:43.0,Tuc -NGC0293,G,00:54:15.96,-07:14:07.7,Cet -NGC0294,OCl,00:53:04.71,-73:22:49.3,Tuc -NGC0295,G,00:59:32.28,+31:47:53.0,Psc -NGC0296,G,00:55:07.51,+31:32:32.2,Psc -NGC0297,G,00:54:58.94,-07:20:58.8,Cet -NGC0298,G,00:55:02.35,-07:19:59.1,Cet -NGC0299,OCl,00:53:24.07,-72:11:49.6,Tuc -NGC0300,G,00:54:53.48,-37:41:03.8,Scl -NGC0301,G,00:56:18.35,-10:40:26.0,Cet -NGC0302,*,00:56:25.31,-10:39:47.6,Cet -NGC0303,G,00:54:54.71,-16:39:16.6,Cet -NGC0304,G,00:56:06.02,+24:07:36.8,And -NGC0305,Other,00:56:20.92,+12:03:54.4,Psc -NGC0306,OCl,00:54:14.18,-72:14:32.4,Tuc -NGC0307,G,00:56:32.58,-01:46:19.0,Cet -NGC0308,*,00:56:34.22,-01:47:02.1,Cet -NGC0309,G,00:56:42.66,-09:54:49.9,Cet -NGC0310,*,00:56:47.98,-01:45:56.8,Cet -NGC0311,G,00:57:32.73,+30:16:50.7,Psc -NGC0312,G,00:56:15.93,-52:46:57.7,Phe -NGC0313,Other,00:57:45.64,+30:21:57.9,Psc -NGC0314,G,00:56:52.41,-31:57:46.7,Scl -NGC0315,G,00:57:48.88,+30:21:08.8,Psc -NGC0316,*,00:57:52.45,+30:21:15.5,Psc -NGC0317,GPair,00:57:39.7,+43:47:47.0,And -NGC0317A,G,00:57:39.05,+43:48:02.8,And -NGC0317B,G,00:57:40.45,+43:47:32.1,And -NGC0318,G,00:58:05.23,+30:25:31.9,Psc -NGC0319,G,00:56:57.60,-43:50:19.6,Phe -NGC0320,G,00:58:46.53,-20:50:24.3,Cet -NGC0321,G,00:57:39.23,-05:05:10.3,Cet -NGC0322,G,00:57:10.01,-43:43:37.5,Phe -NGC0323,G,00:56:41.65,-52:58:33.3,Phe -NGC0324,G,00:57:14.78,-40:57:32.8,Phe -NGC0325,G,00:57:47.87,-05:06:43.5,Cet -NGC0326,GPair,00:58:22.70,+26:51:55.0,Psc -NGC0326 NED01,G,00:58:22.64,+26:51:58.5,Psc -NGC0326 NED02,G,00:58:22.85,+26:51:52.4,Psc -NGC0327,G,00:57:55.36,-05:07:49.5,Cet -NGC0328,G,00:56:57.57,-52:55:26.1,Phe -NGC0329,G,00:58:01.60,-05:04:16.4,Cet -NGC0330,OCl,00:56:17.65,-72:27:46.6,Tuc -NGC0331,G,00:47:06.86,-02:43:52.1,Cet -NGC0332,G,00:58:49.13,+07:06:40.7,Psc -NGC0333,GPair,00:58:51.20,-16:28:13.0,Cet -NGC0333A,G,00:58:51.31,-16:28:09.1,Cet -NGC0333B,G,00:58:51.09,-16:28:17.2,Cet -NGC0334,G,00:58:49.81,-35:06:57.7,Scl -NGC0335,G,00:59:19.78,-18:14:04.8,Cet -NGC0336,G,00:58:02.83,-18:23:03.6,Cet -NGC0337,G,00:59:50.09,-07:34:40.7,Cet -NGC0337A,G,01:01:33.90,-07:35:17.7,Cet -NGC0338,G,01:00:36.41,+30:40:08.3,Psc -NGC0339,GCl,00:57:42.06,-74:28:24.2,Tuc -NGC0340,G,01:00:34.88,-06:51:59.7,Cet -NGC0341,G,01:00:45.83,-09:11:08.5,Cet -NGC0342,G,01:00:49.86,-06:46:21.2,Cet -NGC0343,G,00:58:23.93,-23:13:30.8,Cet -NGC0344,G,00:58:25.44,-23:13:45.2,Cet -NGC0345,G,01:01:22.10,-06:53:03.4,Cet -NGC0346,OCl,00:59:05.04,-72:10:37.6,Tuc -NGC0347,G,01:01:35.16,-06:44:01.2,Cet -NGC0348,G,01:00:52.01,-53:14:40.2,Phe -NGC0349,G,01:01:50.75,-06:47:59.3,Cet -NGC0350,G,01:01:56.71,-06:47:44.6,Cet -NGC0351,G,01:01:57.84,-01:56:12.3,Cet -NGC0352,G,01:02:09.21,-04:14:43.7,Cet -NGC0353,G,01:02:24.53,-01:57:31.9,Cet -NGC0354,G,01:03:16.39,+22:20:33.6,Psc -NGC0355,G,01:03:06.98,-06:19:26.4,Cet -NGC0356,G,01:03:07.10,-06:59:18.1,Cet -NGC0357,G,01:03:21.88,-06:20:21.2,Cet -NGC0358,Other,01:05:10.93,+62:01:13.6,Cas -NGC0359,G,01:04:16.96,-00:45:53.7,Cet -NGC0360,G,01:02:51.45,-65:36:35.9,Tuc -NGC0361,OCl,01:02:10.16,-71:36:17.1,Tuc -NGC0362,GCl,01:03:14.23,-70:50:53.6,Tuc -NGC0363,G,01:06:15.79,-16:32:33.9,Cet -NGC0364,G,01:04:40.83,-00:48:09.9,Cet -NGC0365,G,01:04:18.72,-35:07:17.1,Scl -NGC0366,OCl,01:06:25.99,+62:13:44.1,Cas -NGC0367,G,01:05:48.89,-12:07:42.5,Cet -NGC0368,G,01:04:22.04,-43:16:36.3,Phe -NGC0369,G,01:05:08.91,-17:45:33.1,Cet -NGC0370,Other,01:06:44.59,+32:25:43.3,Psc -NGC0371,OCl,01:03:29.53,-72:03:24.6,Tuc -NGC0372,Dup,01:06:44.59,+32:25:43.3,Psc -NGC0373,G,01:06:58.21,+32:18:30.5,Psc -NGC0374,G,01:07:05.78,+32:47:42.4,Psc -NGC0375,G,01:07:05.92,+32:20:53.4,Psc -NGC0376,OCl,01:03:53.45,-72:49:25.2,Tuc -NGC0377,G,01:06:34.82,-20:19:57.2,Cet -NGC0378,G,01:06:12.21,-30:10:41.3,Scl -NGC0379,G,01:07:15.69,+32:31:13.3,Psc -NGC0380,G,01:07:17.59,+32:28:58.5,Psc -NGC0381,OCl,01:08:18.01,+61:34:59.8,Cas -NGC0382,G,01:07:23.87,+32:24:13.9,Psc -NGC0383,G,01:07:24.96,+32:24:45.2,Psc -NGC0384,G,01:07:25.10,+32:17:32.8,Psc -NGC0385,G,01:07:27.25,+32:19:10.3,Psc -NGC0386,G,01:07:31.29,+32:21:43.2,Psc -NGC0387,G,01:07:33.06,+32:23:28.0,Psc -NGC0388,G,01:07:47.15,+32:18:35.9,Psc -NGC0389,G,01:08:29.93,+39:41:43.6,And -NGC0390,*,01:07:53.75,+32:25:59.2,Psc -NGC0391,G,01:07:22.58,+00:55:33.4,Cet -NGC0392,G,01:08:23.46,+33:08:01.0,Psc -NGC0393,G,01:08:36.95,+39:38:39.5,And -NGC0394,G,01:08:26.05,+33:08:52.7,Psc -NGC0395,OCl,01:05:08.32,-71:59:26.6,Tuc -NGC0396,G,01:08:08.40,+04:31:51.1,Psc -NGC0397,G,01:08:31.10,+33:06:33.1,Psc -NGC0398,G,01:08:53.67,+32:30:52.3,Psc -NGC0399,G,01:08:59.20,+32:38:03.5,Psc -NGC0400,*,01:09:02.49,+32:43:56.7,Psc -NGC0401,*,01:09:07.72,+32:45:33.6,Psc -NGC0402,*,01:09:13.36,+32:48:22.5,Psc -NGC0403,G,01:09:14.16,+32:45:07.7,Psc -NGC0404,G,01:09:27.02,+35:43:05.3,And -NGC0405,Other,01:08:34.11,-46:40:06.6,Phe -NGC0406,G,01:07:25.05,-69:52:45.0,Tuc -NGC0407,G,01:10:36.56,+33:07:35.4,Psc -NGC0408,*,01:10:51.09,+33:09:04.6,Psc -NGC0409,G,01:09:33.22,-35:48:20.2,Scl -NGC0410,G,01:10:58.90,+33:09:06.8,Psc -NGC0411,OCl,01:07:55.67,-71:46:06.1,Tuc -NGC0412,NonEx,01:10:20.48,-20:00:56.9,Cet -NGC0413,G,01:12:31.40,-02:47:36.3,Cet -NGC0414,GPair,01:11:17.80,+33:06:46.0,Psc -NGC0414 NED01,G,01:11:17.48,+33:06:50.0,Psc -NGC0414 NED02,G,01:11:17.95,+33:06:44.1,Psc -NGC0415,G,01:10:05.69,-35:29:26.8,Scl -NGC0416,GCl,01:07:58.50,-72:21:18.2,Tuc -NGC0417,G,01:11:05.56,-18:08:54.0,Cet -NGC0418,G,01:10:35.62,-30:13:16.6,Scl -NGC0419,GCl,01:08:17.17,-72:53:00.6,Tuc -NGC0420,G,01:12:09.65,+32:07:23.4,Psc -NGC0421,Other,01:12:14.46,+32:07:24.6,Psc -NGC0422,OCl,01:09:25.39,-71:46:02.0,Tuc -NGC0423,G,01:11:22.21,-29:14:04.3,Scl -NGC0424,G,01:11:27.63,-38:05:00.5,Scl -NGC0425,G,01:13:02.56,+38:46:06.1,And -NGC0426,G,01:12:48.61,-00:17:24.7,Cet -NGC0427,G,01:12:19.24,-32:03:40.2,Scl -NGC0428,G,01:12:55.71,+00:58:53.6,Cet -NGC0429,G,01:12:57.42,-00:20:42.0,Cet -NGC0430,G,01:12:59.93,-00:15:09.0,Cet -NGC0431,G,01:14:04.54,+33:42:15.1,And -NGC0432,G,01:11:46.26,-61:31:39.5,Tuc -NGC0433,OCl,01:15:09.26,+60:07:32.8,Cas -NGC0434,G,01:12:14.13,-58:14:52.7,Tuc -NGC0434A,G,01:12:29.62,-58:12:34.2,Tuc -NGC0435,G,01:13:59.85,+02:04:17.2,Cet -NGC0436,OCl,01:15:57.78,+58:49:01.6,Cas -NGC0437,G,01:14:22.29,+05:55:36.8,Psc -NGC0438,G,01:13:34.16,-37:54:05.9,Scl -NGC0439,G,01:13:47.26,-31:44:49.7,Scl -NGC0440,G,01:12:48.50,-58:16:56.2,Tuc -NGC0441,G,01:13:51.26,-31:47:18.0,Scl -NGC0442,G,01:14:38.65,-01:01:14.3,Cet -NGC0443,G,01:15:07.59,+33:22:38.4,Psc -NGC0444,G,01:15:49.60,+31:04:48.9,Psc -NGC0445,G,01:14:52.48,+01:55:02.8,Cet -NGC0446,G,01:16:03.61,+04:17:38.8,Psc -NGC0447,G,01:15:37.63,+33:04:03.8,Psc -NGC0448,G,01:15:16.52,-01:37:34.3,Cet -NGC0449,G,01:16:07.25,+33:05:22.4,Psc -NGC0450,G,01:15:30.44,-00:51:39.5,Cet -NGC0451,G,01:16:12.40,+33:03:50.8,Psc -NGC0452,G,01:16:14.83,+31:02:01.8,Psc -NGC0453,Other,01:16:17.42,+33:00:51.0,Psc -NGC0454,GPair,01:14:22.53,-55:23:55.3,Phe -NGC0454 NED01,G,01:14:21.81,-55:24:05.1,Phe -NGC0454 NED02,G,01:14:24.93,-55:23:49.5,Phe -NGC0455,G,01:15:57.64,+05:10:43.3,Psc -NGC0456,OCl,01:13:44.38,-73:17:25.9,Tuc -NGC0457,OCl,01:19:32.65,+58:17:26.5,Cas -NGC0458,OCl,01:14:53.47,-71:33:09.7,Tuc -NGC0459,G,01:18:08.19,+17:33:44.6,Psc -NGC0460,Cl+N,01:14:38.60,-73:16:27.1,Tuc -NGC0461,G,01:17:20.64,-33:50:27.2,Scl -NGC0462,G,01:18:10.99,+04:13:33.6,Psc -NGC0463,G,01:18:58.25,+16:19:32.7,Psc -NGC0464,**,01:19:26.68,+34:57:19.5,And -NGC0465,OCl,01:15:41.35,-73:20:04.6,Tuc -NGC0466,G,01:17:13.26,-58:54:35.8,Tuc -NGC0467,G,01:19:10.13,+03:18:03.0,Psc -NGC0468,G,01:19:48.50,+32:46:04.0,Psc -NGC0469,G,01:19:32.95,+14:52:19.0,Psc -NGC0470,G,01:19:44.85,+03:24:35.8,Psc -NGC0471,G,01:19:59.59,+14:47:10.4,Psc -NGC0472,G,01:20:28.69,+32:42:32.5,Psc -NGC0473,G,01:19:55.07,+16:32:40.9,Psc -NGC0474,G,01:20:06.69,+03:24:55.4,Psc -NGC0475,G,01:20:02.00,+14:51:39.8,Psc -NGC0476,G,01:20:19.90,+16:01:12.5,Psc -NGC0477,G,01:21:20.37,+40:29:17.5,And -NGC0478,G,01:20:09.29,-22:22:38.6,Cet -NGC0479,G,01:21:15.73,+03:51:44.2,Psc -NGC0480,G,01:20:34.39,-09:52:48.8,Cet -NGC0481,G,01:21:12.51,-09:12:40.4,Cet -NGC0482,G,01:20:20.42,-40:57:58.0,Phe -NGC0483,G,01:21:56.30,+33:31:15.5,Psc -NGC0484,G,01:19:34.73,-58:31:27.5,Tuc -NGC0485,G,01:21:27.60,+07:01:05.1,Psc -NGC0486,G,01:21:43.04,+05:20:46.7,Psc -NGC0487,G,01:21:55.09,-16:22:13.3,Cet -NGC0488,G,01:21:46.85,+05:15:24.2,Psc -NGC0489,G,01:21:53.90,+09:12:23.6,Psc -NGC0490,G,01:22:02.87,+05:22:01.9,Psc -NGC0491,G,01:21:20.43,-34:03:47.8,Scl -NGC0491A,G,01:20:04.67,-33:53:58.2,Scl -NGC0492,G,01:22:13.56,+05:25:01.4,Psc -NGC0493,G,01:22:08.99,+00:56:43.3,Cet -NGC0494,G,01:22:55.40,+33:10:26.0,Psc -NGC0495,G,01:22:56.00,+33:28:18.0,Psc -NGC0496,G,01:23:11.60,+33:31:45.0,Psc -NGC0497,G,01:22:23.78,-00:52:30.7,Cet -NGC0498,G,01:23:11.28,+33:29:21.7,Psc -NGC0499,G,01:23:11.50,+33:27:38.0,Psc -NGC0500,G,01:22:39.37,+05:23:14.2,Psc -NGC0501,G,01:23:22.41,+33:25:58.7,Psc -NGC0502,G,01:22:55.54,+09:02:57.1,Psc -NGC0503,G,01:23:28.43,+33:19:54.2,Psc -NGC0504,G,01:23:27.90,+33:12:16.0,Psc -NGC0505,G,01:22:57.10,+09:28:08.0,Psc -NGC0506,*,01:23:35.35,+33:14:40.8,Psc -NGC0507,G,01:23:39.91,+33:15:21.8,Psc -NGC0508,G,01:23:40.60,+33:16:49.0,Psc -NGC0509,G,01:23:24.09,+09:26:00.8,Psc -NGC0510,**,01:23:55.56,+33:29:48.8,Psc -NGC0511,G,01:23:30.73,+11:17:27.6,Psc -NGC0512,G,01:23:59.80,+33:54:28.0,And -NGC0513,G,01:24:26.85,+33:47:58.0,And -NGC0514,G,01:24:03.90,+12:55:02.6,Psc -NGC0515,G,01:24:38.60,+33:28:22.0,Psc -NGC0516,G,01:24:08.07,+09:33:06.1,Psc -NGC0517,G,01:24:43.80,+33:25:46.0,Psc -NGC0518,G,01:24:17.64,+09:19:51.4,Psc -NGC0519,G,01:24:28.64,-01:38:28.5,Cet -NGC0520,GPair,01:24:35.07,+03:47:32.7,Psc -NGC0520 NED01,G,01:24:34.48,+03:47:41.8,Psc -NGC0520 NED02,G,01:24:34.79,+03:47:23.2,Psc -NGC0521,G,01:24:33.78,+01:43:53.0,Cet -NGC0522,G,01:24:45.85,+09:59:40.7,Psc -NGC0523,G,01:25:20.73,+34:01:29.8,And -NGC0524,G,01:24:47.72,+09:32:19.8,Psc -NGC0525,G,01:24:52.91,+09:42:12.0,Psc -NGC0526,GPair,01:23:58.50,-35:07:21.0,Scl -NGC0526A,G,01:23:54.39,-35:03:55.9,Scl -NGC0526B,G,01:23:57.08,-35:04:09.4,Scl -NGC0527,G,01:23:58.12,-35:06:54.2,Scl -NGC0527B,G,01:23:59.34,-35:07:38.5,Scl -NGC0528,G,01:25:33.60,+33:40:18.0,And -NGC0529,G,01:25:40.40,+34:42:48.0,And -NGC0530,G,01:24:41.65,-01:35:13.5,Cet -NGC0531,G,01:26:18.90,+34:45:14.0,And -NGC0532,G,01:25:17.34,+09:15:50.8,Psc -NGC0533,G,01:25:31.36,+01:45:32.8,Cet -NGC0534,G,01:24:44.63,-38:07:44.7,Scl -NGC0535,G,01:25:31.15,-01:24:29.3,Cet -NGC0536,G,01:26:21.78,+34:42:10.9,And -NGC0537,Dup,01:25:20.73,+34:01:29.8,And -NGC0538,G,01:25:26.05,-01:33:02.3,Cet -NGC0539,G,01:25:21.73,-18:09:49.9,Cet -NGC0540,G,01:27:08.90,-20:02:11.9,Cet -NGC0541,G,01:25:44.31,-01:22:46.5,Cet -NGC0542,G,01:26:30.90,+34:40:31.0,And -NGC0543,G,01:25:49.99,-01:17:34.1,Cet -NGC0544,G,01:25:12.03,-38:05:40.3,Scl -NGC0545,G,01:25:59.12,-01:20:24.8,Cet -NGC0546,G,01:25:12.81,-38:04:08.7,Scl -NGC0547,G,01:26:00.63,-01:20:42.6,Cet -NGC0548,G,01:26:02.51,-01:13:32.2,Cet -NGC0549,G,01:25:07.08,-38:00:27.9,Scl -NGC0550,G,01:26:42.55,+02:01:20.5,Cet -NGC0551,G,01:27:40.66,+37:10:58.5,And -NGC0552,*,01:26:10.15,+33:24:21.1,Psc -NGC0553,G,01:26:12.60,+33:24:18.0,Psc -NGC0554,GPair,01:27:09.60,-22:43:30.0,Cet -NGC0554A,G,01:27:09.70,-22:43:28.8,Cet -NGC0554B,G,01:27:09.56,-22:43:32.6,Cet -NGC0555,G,01:27:11.81,-22:45:43.8,Cet -NGC0556,G,01:27:12.61,-22:41:51.9,Cet -NGC0557,G,01:26:25.15,-01:38:19.4,Cet -NGC0558,G,01:27:16.17,-01:58:15.2,Cet -NGC0559,OCl,01:29:33.17,+63:18:05.2,Cas -NGC0560,G,01:27:25.42,-01:54:46.7,Cet -NGC0561,G,01:28:18.76,+34:18:31.1,And -NGC0562,G,01:28:29.26,+48:23:13.5,And -NGC0563,Dup,01:25:21.73,-18:09:49.9,Cet -NGC0564,G,01:27:48.21,-01:52:46.3,Cet -NGC0565,G,01:28:10.18,-01:18:21.7,Cet -NGC0566,G,01:29:02.96,+32:19:56.1,Psc -NGC0567,G,01:27:02.40,-10:15:54.9,Cet -NGC0568,G,01:27:57.01,-35:43:03.7,Scl -NGC0569,G,01:29:07.16,+11:07:53.3,Psc -NGC0570,G,01:28:58.64,-00:56:56.4,Cet -NGC0571,G,01:29:55.98,+32:30:04.8,Psc -NGC0572,G,01:28:36.42,-39:18:26.3,Scl -NGC0573,G,01:30:49.35,+41:15:26.3,And -NGC0574,G,01:29:03.08,-35:35:56.1,Scl -NGC0575,G,01:30:46.64,+21:26:25.5,Psc -NGC0576,G,01:28:57.67,-51:35:55.2,Phe -NGC0577,G,01:30:40.71,-01:59:39.7,Cet -NGC0578,G,01:30:29.09,-22:40:02.5,Cet -NGC0579,G,01:31:46.49,+33:36:56.0,Tri -NGC0580,Dup,01:30:40.71,-01:59:39.7,Cet -NGC0581,OCl,01:33:21.81,+60:39:28.8,Cas -NGC0582,G,01:31:58.05,+33:28:35.5,Tri -NGC0583,G,01:29:44.15,-18:20:21.9,Cet -NGC0584,G,01:31:20.75,-06:52:05.0,Cet -NGC0585,G,01:31:42.14,-00:55:59.9,Cet -NGC0586,G,01:31:36.85,-06:53:37.5,Cet -NGC0587,G,01:32:33.33,+35:21:30.9,Tri -NGC0588,HII,01:32:45.94,+30:38:51.1,Tri -NGC0589,G,01:32:39.94,-12:02:33.7,Cet -NGC0590,G,01:33:40.92,+44:55:43.3,And -NGC0591,G,01:33:31.27,+35:40:05.7,And -NGC0592,*Ass,01:33:11.69,+30:38:41.8,Tri -NGC0593,G,01:32:20.76,-12:21:16.0,Cet -NGC0594,G,01:32:56.91,-16:32:09.6,Cet -NGC0595,HII,01:33:33.83,+30:41:29.6,Tri -NGC0596,G,01:32:52.08,-07:01:54.6,Cet -NGC0597,G,01:32:14.85,-33:29:49.5,Scl -NGC0598,G,01:33:50.89,+30:39:36.8,Tri -NGC0599,G,01:32:53.78,-12:11:28.5,Cet -NGC0600,G,01:33:05.30,-07:18:41.1,Cet -NGC0601,G,01:33:06.58,-12:12:31.5,Cet -NGC0602,OCl,01:29:26.36,-73:33:37.8,Hyi -NGC0603,Other,01:34:43.90,+30:13:55.1,Tri -NGC0604,HII,01:34:33.19,+30:47:05.6,Tri -NGC0605,G,01:35:02.35,+41:14:53.3,And -NGC0606,G,01:34:50.16,+21:25:06.4,Psc -NGC0607,Other,01:34:16.37,-07:24:46.1,Cet -NGC0608,G,01:35:28.24,+33:39:24.2,Tri -NGC0609,OCl,01:36:23.74,+64:32:11.7,Cas -NGC0610,Other,01:34:18.00,-20:08:39.2,Cet -NGC0611,Other,01:34:18.01,-20:07:39.2,Cet -NGC0612,G,01:33:57.74,-36:29:35.7,Scl -NGC0613,G,01:34:18.17,-29:25:06.1,Scl -NGC0614,G,01:35:52.26,+33:40:54.8,Tri -NGC0615,G,01:35:05.68,-07:20:25.1,Cet -NGC0616,**,01:36:04.20,+33:46:12.8,Tri -NGC0617,G,01:34:02.54,-09:46:26.9,Cet -NGC0618,Dup,01:35:52.26,+33:40:54.8,Tri -NGC0619,G,01:34:51.79,-36:29:21.8,Scl -NGC0620,G,01:36:59.68,+42:19:24.0,And -NGC0621,G,01:36:49.06,+35:30:43.9,Tri -NGC0622,G,01:36:00.16,+00:39:48.7,Cet -NGC0623,G,01:35:06.39,-36:29:24.8,Scl -NGC0624,G,01:35:51.09,-10:00:10.7,Cet -NGC0625,G,01:35:04.63,-41:26:10.3,Phe -NGC0626,G,01:35:12.08,-39:08:45.8,Scl -NGC0627,Dup,01:35:52.26,+33:40:54.8,Tri -NGC0628,G,01:36:41.75,+15:47:01.2,Psc -NGC0629,Other,01:38:58.54,+72:52:01.5,Cas -NGC0630,G,01:35:36.48,-39:21:28.2,Scl -NGC0631,G,01:36:47.06,+05:50:07.3,Psc -NGC0632,G,01:37:17.53,+05:52:39.5,Psc -NGC0633,G,01:36:23.40,-37:19:17.6,Scl -NGC0634,G,01:38:18.66,+35:21:53.4,Tri -NGC0635,G,01:38:17.86,-22:55:44.1,Cet -NGC0636,G,01:39:06.53,-07:30:45.4,Cet -NGC0637,OCl,01:43:03.11,+64:02:11.6,Cas -NGC0638,G,01:39:37.85,+07:14:14.4,Psc -NGC0639,G,01:38:59.05,-29:55:31.2,Scl -NGC0640,G,01:39:24.88,-09:24:04.2,Cet -NGC0641,G,01:38:39.23,-42:31:39.1,Phe -NGC0642,G,01:39:06.33,-29:54:53.6,Scl -NGC0643,OCl,01:35:01.43,-75:33:24.7,Hyi -NGC0643A,OCl,01:30:38.49,-76:03:16.2,Hyi -NGC0643B,G,01:39:12.73,-75:00:40.0,Hyi -NGC0643C,G,01:41:49.13,-75:16:05.2,Hyi -NGC0644,G,01:38:52.96,-42:35:07.0,Phe -NGC0645,G,01:40:08.70,+05:43:36.1,Psc -NGC0646,GPair,01:37:25.50,-64:53:47.0,Hyi -NGC0646 NED01,G,01:37:21.12,-64:53:41.5,Hyi -NGC0646 NED02,G,01:37:29.89,-64:53:46.6,Hyi -NGC0647,G,01:39:56.19,-09:14:32.6,Cet -NGC0648,G,01:38:39.79,-17:49:52.3,Cet -NGC0649,G,01:40:07.45,-09:16:19.6,Cet -NGC0650,PN,01:42:19.69,+51:34:31.7,Per -NGC0651,Dup,01:42:19.69,+51:34:31.7,Per -NGC0652,G,01:40:43.28,+07:58:58.5,Psc -NGC0653,G,01:42:25.74,+35:38:18.0,And -NGC0654,OCl,01:43:59.43,+61:52:57.8,Cas -NGC0655,G,01:41:55.13,-13:04:54.3,Cet -NGC0656,G,01:42:27.24,+26:08:35.0,Psc -NGC0657,OCl,01:43:20.80,+55:50:11.0,Cas -NGC0658,G,01:42:09.66,+12:36:06.8,Psc -NGC0659,OCl,01:44:22.99,+60:40:09.0,Cas -NGC0660,G,01:43:02.40,+13:38:42.2,Psc -NGC0661,G,01:44:14.62,+28:42:21.3,Tri -NGC0662,G,01:44:35.45,+37:41:44.8,And -NGC0663,OCl,01:46:16.05,+61:13:05.5,Cas -NGC0664,G,01:43:45.81,+04:13:22.4,Psc -NGC0665,G,01:44:56.10,+10:25:22.9,Psc -NGC0666,G,01:46:06.20,+34:22:28.3,Tri -NGC0667,G,01:44:56.68,-22:55:08.2,Cet -NGC0668,G,01:46:22.67,+36:27:37.1,And -NGC0669,G,01:47:16.15,+35:33:47.9,Tri -NGC0670,G,01:47:24.85,+27:53:08.7,Tri -NGC0671,G,01:46:59.17,+13:07:30.4,Ari -NGC0672,G,01:47:54.52,+27:25:58.0,Tri -NGC0673,G,01:48:22.44,+11:31:16.7,Ari -NGC0674,G,01:51:17.57,+22:21:28.7,Ari -NGC0675,G,01:49:08.61,+13:03:35.6,Ari -NGC0676,G,01:48:57.31,+05:54:27.1,Psc -NGC0677,G,01:49:14.06,+13:03:19.3,Ari -NGC0678,G,01:49:24.86,+21:59:50.3,Ari -NGC0679,G,01:49:43.78,+35:47:08.3,And -NGC0680,G,01:49:47.29,+21:58:15.1,Ari -NGC0681,G,01:49:10.83,-10:25:35.1,Cet -NGC0682,G,01:49:04.60,-14:58:29.4,Cet -NGC0683,G,01:49:46.69,+11:42:04.7,Ari -NGC0684,G,01:50:14.02,+27:38:44.4,Tri -NGC0685,G,01:47:42.81,-52:45:42.5,Eri -NGC0686,G,01:48:56.14,-23:47:53.3,For -NGC0687,G,01:50:33.24,+36:22:14.9,And -NGC0688,G,01:50:44.18,+35:17:04.3,Tri -NGC0689,G,01:49:51.77,-27:27:59.8,For -NGC0690,G,01:47:48.08,-16:43:19.2,Cet -NGC0691,G,01:50:41.72,+21:45:35.7,Ari -NGC0692,G,01:48:41.99,-48:38:54.6,Phe -NGC0693,G,01:50:30.85,+06:08:42.8,Psc -NGC0694,G,01:50:58.49,+21:59:51.0,Ari -NGC0695,G,01:51:14.24,+22:34:56.5,Ari -NGC0696,G,01:49:31.26,-34:54:18.6,For -NGC0697,Dup,01:51:17.57,+22:21:28.7,Ari -NGC0698,G,01:49:43.70,-34:49:51.9,For -NGC0699,G,01:50:43.68,-12:02:08.4,Cet -NGC0700,G,01:52:16.84,+36:02:12.2,And -NGC0701,G,01:51:03.84,-09:42:09.4,Cet -NGC0702,GPair,01:51:18.80,-04:03:07.0,Cet -NGC0702 NED01,G,01:51:18.39,-04:02:57.0,Cet -NGC0702 NED02,G,01:51:19.19,-04:03:21.4,Cet -NGC0703,G,01:52:39.60,+36:10:17.1,And -NGC0704,GPair,01:52:38.60,+36:07:27.0,And -NGC0704A,G,01:52:39.98,+36:07:15.4,And -NGC0704B,G,01:52:37.70,+36:07:36.5,And -NGC0705,G,01:52:41.52,+36:08:38.4,And -NGC0706,G,01:51:50.53,+06:17:48.8,Psc -NGC0707,G,01:51:27.10,-08:30:19.3,Cet -NGC0708,G,01:52:46.48,+36:09:06.6,And -NGC0709,G,01:52:50.62,+36:13:24.4,And -NGC0710,G,01:52:53.94,+36:03:10.4,And -NGC0711,G,01:52:27.76,+17:30:45.6,Ari -NGC0712,G,01:53:08.44,+36:49:11.5,And -NGC0713,G,01:55:21.53,-09:05:01.5,Cet -NGC0714,G,01:53:29.65,+36:13:16.7,And -NGC0715,G,01:53:12.52,-12:52:22.2,Cet -NGC0716,G,01:52:59.68,+12:42:30.5,Ari -NGC0717,G,01:53:55.11,+36:13:45.9,And -NGC0718,G,01:53:13.30,+04:11:45.0,Psc -NGC0719,G,01:53:38.84,+19:50:25.5,Ari -NGC0720,G,01:53:00.50,-13:44:19.2,Cet -NGC0721,G,01:54:45.46,+39:23:00.7,And -NGC0722,G,01:54:46.95,+20:41:53.6,Ari -NGC0723,G,01:53:45.68,-23:45:27.9,Cet -NGC0724,Dup,01:53:45.68,-23:45:27.9,Cet -NGC0725,G,01:52:35.49,-16:31:04.1,Cet -NGC0726,G,01:55:31.87,-10:47:59.3,Cet -NGC0727,G,01:53:49.38,-35:51:22.2,For -NGC0728,Other,01:55:01.44,+04:13:21.3,Psc -NGC0729,Dup,01:53:49.38,-35:51:22.2,For -NGC0730,*,01:55:18.00,+05:38:11.0,Psc -NGC0731,G,01:54:56.21,-09:00:38.9,Cet -NGC0732,G,01:56:27.71,+36:48:08.0,And -NGC0733,*,01:56:33.89,+33:03:19.1,Tri -NGC0734,G,01:53:28.74,-16:59:44.5,Cet -NGC0735,G,01:56:37.98,+34:10:36.4,Tri -NGC0736,G,01:56:40.87,+33:02:36.6,Tri -NGC0737,Other,01:56:40.80,+33:02:56.1,Tri -NGC0738,G,01:56:45.69,+33:03:30.0,Tri -NGC0739,G,01:56:54.69,+33:16:00.1,Tri -NGC0740,G,01:56:54.87,+33:00:54.6,Tri -NGC0741,G,01:56:21.03,+05:37:44.2,Psc -NGC0742,G,01:56:24.17,+05:37:36.1,Psc -NGC0743,OCl,01:58:31.29,+60:09:58.7,Cas -NGC0744,OCl,01:58:29.92,+55:28:28.6,Per -NGC0745,GGroup,01:54:08.66,-56:41:26.7,Eri -NGC0745 NED01,G,01:54:07.72,-56:41:37.2,Eri -NGC0745 NED02,G,01:54:07.61,-56:41:14.5,Eri -NGC0745 NED03,G,01:54:11.33,-56:41:07.7,Eri -NGC0746,G,01:57:51.01,+44:55:06.9,And -NGC0747,G,01:57:30.45,-09:27:44.5,Cet -NGC0748,G,01:56:21.80,-04:28:03.5,Cet -NGC0749,G,01:55:41.12,-29:55:20.4,For -NGC0750,G,01:57:32.73,+33:12:33.4,Tri -NGC0751,G,01:57:32.99,+33:12:11.1,Tri -NGC0752,OCl,01:57:34.82,+37:50:00.2,And -NGC0753,G,01:57:42.20,+35:54:58.0,And -NGC0754,G,01:54:20.84,-56:45:39.8,Eri -NGC0755,G,01:56:22.68,-09:03:41.1,Cet -NGC0756,GPair,01:54:29.05,-16:42:25.8,Cet -NGC0757,Dup,01:54:56.21,-09:00:38.9,Cet -NGC0758,G,01:55:42.14,-03:03:59.3,Cet -NGC0759,G,01:57:50.33,+36:20:35.2,And -NGC0760,**,01:57:47.40,+33:21:19.4,Tri -NGC0761,G,01:57:49.59,+33:22:37.7,Tri -NGC0762,G,01:56:57.79,-05:24:10.3,Cet -NGC0763,Dup,01:56:22.68,-09:03:41.1,Cet -NGC0764,**,01:57:03.27,-16:03:45.0,Cet -NGC0765,G,01:58:48.00,+24:53:32.9,Ari -NGC0766,G,01:58:41.95,+08:20:47.9,Psc -NGC0767,G,01:58:50.80,-09:35:13.7,Cet -NGC0768,G,01:58:40.93,+00:31:45.2,Cet -NGC0769,G,01:59:35.90,+30:54:35.7,Tri -NGC0770,G,01:59:13.64,+18:57:16.8,Ari -NGC0771,*,02:03:26.59,+72:25:15.2,Cas -NGC0772,G,01:59:19.58,+19:00:27.1,Ari -NGC0773,G,01:58:52.00,-11:30:52.7,Cet -NGC0774,G,01:59:34.73,+14:00:29.5,Ari -NGC0775,G,01:58:32.67,-26:17:37.4,For -NGC0776,G,01:59:54.49,+23:38:39.8,Ari -NGC0777,G,02:00:14.90,+31:25:46.5,Tri -NGC0778,G,02:00:19.44,+31:18:46.9,Tri -NGC0779,G,01:59:42.28,-05:57:47.5,Cet -NGC0780,G,02:00:35.18,+28:13:30.5,Tri -NGC0781,G,02:00:08.96,+12:39:22.0,Ari -NGC0782,G,01:57:40.38,-57:47:24.6,Eri -NGC0783,G,02:01:06.61,+31:52:56.9,Tri -NGC0784,G,02:01:16.93,+28:50:14.1,Tri -NGC0785,G,02:01:40.00,+31:49:35.4,Tri -NGC0786,G,02:01:24.72,+15:38:47.5,Ari -NGC0787,G,02:00:48.62,-09:00:09.3,Cet -NGC0788,G,02:01:06.45,-06:48:55.9,Cet -NGC0789,G,02:02:26.01,+32:04:20.1,Tri -NGC0790,G,02:01:21.60,-05:22:15.5,Cet -NGC0791,G,02:01:44.22,+08:29:59.8,Psc -NGC0792,G,02:02:15.33,+15:42:43.9,Ari -NGC0793,**,02:02:54.54,+31:58:50.6,Tri -NGC0794,G,02:02:29.33,+18:22:22.8,Ari -NGC0795,G,01:59:49.36,-55:49:27.0,Eri -NGC0796,OCl,01:56:43.75,-74:13:12.0,Hyi -NGC0797,GPair,02:03:26.30,+38:06:52.0,And -NGC0797 NED01,G,02:03:24.55,+38:06:44.0,And -NGC0797 NED02,G,02:03:27.94,+38:07:01.0,And -NGC0798,G,02:03:19.60,+32:04:39.1,Tri -NGC0799,G,02:02:12.34,-00:06:02.3,Cet -NGC0800,G,02:02:11.85,-00:07:49.6,Cet -NGC0801,G,02:03:44.83,+38:15:31.4,And -NGC0802,G,01:59:06.00,-67:52:12.5,Hyi -NGC0803,G,02:03:44.70,+16:01:51.5,Ari -NGC0804,G,02:04:02.11,+30:49:58.3,Tri -NGC0805,G,02:04:29.57,+28:48:44.4,Tri -NGC0806,G,02:03:31.15,-09:56:00.1,Cet -NGC0807,G,02:04:55.66,+28:59:14.8,Tri -NGC0808,G,02:03:56.60,-23:18:41.8,Cet -NGC0809,G,02:04:18.97,-08:44:07.1,Cet -NGC0810,GPair,02:05:28.90,+13:15:05.0,Ari -NGC0810 NED01,G,02:05:28.51,+13:15:04.4,Ari -NGC0810 NED02,G,02:05:29.29,+13:15:03.5,Ari -NGC0811,G,02:04:34.84,-10:06:30.5,Cet -NGC0812,G,02:06:51.50,+44:34:22.5,And -NGC0813,G,02:01:36.07,-68:26:21.1,Hyi -NGC0814,G,02:10:37.63,-15:46:24.9,Cet -NGC0815,GPair,02:10:39.40,-15:48:46.0,Cet -NGC0815 NED01,G,02:10:39.20,-15:48:44.7,Cet -NGC0815 NED02,G,02:10:39.51,-15:48:48.2,Cet -NGC0816,G,02:08:08.85,+29:15:21.0,Tri -NGC0817,G,02:07:33.71,+17:12:09.5,Ari -NGC0818,G,02:08:44.52,+38:46:37.9,And -NGC0819,G,02:08:34.37,+29:14:02.5,Tri -NGC0820,G,02:08:24.98,+14:20:58.4,Ari -NGC0821,G,02:08:21.14,+10:59:41.7,Ari -NGC0822,G,02:06:39.14,-41:09:24.3,Phe -NGC0823,G,02:07:20.05,-25:26:30.8,For -NGC0824,G,02:06:53.26,-36:27:11.4,For -NGC0825,G,02:08:32.36,+06:19:25.4,Cet -NGC0826,G,02:09:25.05,+30:44:22.9,Tri -NGC0827,G,02:08:56.26,+07:58:17.3,Cet -NGC0828,G,02:10:09.57,+39:11:25.3,And -NGC0829,G,02:08:42.38,-07:47:25.9,Cet -NGC0830,G,02:08:58.68,-07:46:00.5,Cet -NGC0831,G,02:09:34.60,+06:05:46.8,Cet -NGC0832,**,02:11:00.83,+35:32:28.5,Tri -NGC0833,G,02:09:20.84,-10:07:59.1,Cet -NGC0834,G,02:11:01.29,+37:39:58.6,And -NGC0835,G,02:09:24.60,-10:08:09.3,Cet -NGC0836,G,02:10:24.88,-22:03:17.5,Cet -NGC0837,G,02:10:16.25,-22:25:53.4,Cet -NGC0838,G,02:09:38.53,-10:08:48.1,Cet -NGC0839,G,02:09:42.93,-10:11:02.7,Cet -NGC0840,G,02:10:16.21,+07:50:43.1,Cet -NGC0841,G,02:11:17.36,+37:29:49.8,And -NGC0842,G,02:09:50.79,-07:45:44.9,Cet -NGC0843,Other,02:11:08.11,+32:05:50.7,Tri -NGC0844,G,02:10:14.25,+06:02:59.3,Cet -NGC0845,G,02:12:19.79,+37:28:38.4,And -NGC0846,G,02:12:12.32,+44:34:06.2,And -NGC0847,Dup,02:12:12.32,+44:34:06.2,And -NGC0848,G,02:10:17.64,-10:19:17.2,Cet -NGC0849,G,02:10:11.19,-22:19:22.8,Cet -NGC0850,G,02:11:13.61,-01:29:08.1,Cet -NGC0851,G,02:11:12.09,+03:46:46.9,Cet -NGC0852,G,02:08:55.46,-56:44:13.4,Eri -NGC0853,G,02:11:41.19,-09:18:21.6,Cet -NGC0854,G,02:11:30.75,-35:50:06.4,For -NGC0855,G,02:14:03.49,+27:52:38.4,Tri -NGC0856,G,02:13:38.36,-00:43:02.2,Cet -NGC0857,G,02:12:36.97,-31:56:40.6,For -NGC0858,G,02:12:30.17,-22:28:17.5,Cet -NGC0859,Dup,02:13:38.36,-00:43:02.2,Cet -NGC0860,G,02:15:00.15,+30:46:43.7,Tri -NGC0861,G,02:15:51.14,+35:54:48.9,Tri -NGC0862,G,02:13:03.00,-42:02:00.7,Phe -NGC0863,G,02:14:33.56,-00:46:00.1,Cet -NGC0864,G,02:15:27.64,+06:00:09.4,Cet -NGC0865,G,02:16:15.11,+28:35:59.0,Tri -NGC0866,Dup,02:14:33.56,-00:46:00.1,Cet -NGC0867,G,02:17:04.78,+01:14:39.1,Cet -NGC0868,G,02:15:58.48,-00:42:49.1,Cet -NGC0869,OCl,02:18:58.56,+57:07:02.1,Per -NGC0870,G,02:17:09.22,+14:31:23.2,Ari -NGC0871,G,02:17:10.73,+14:32:52.2,Ari -NGC0872,G,02:15:25.23,-17:46:51.8,Cet -NGC0873,G,02:16:32.36,-11:20:54.8,Cet -NGC0874,G,02:16:01.15,-23:18:06.2,Cet -NGC0875,Dup,02:17:04.78,+01:14:39.1,Cet -NGC0876,G,02:17:53.31,+14:31:16.6,Ari -NGC0877,G,02:17:59.64,+14:32:38.6,Ari -NGC0878,G,02:17:54.26,-23:23:02.6,Cet -NGC0879,G,02:16:51.20,-08:57:50.5,Cet -NGC0880,G,02:18:27.18,-04:12:20.7,Cet -NGC0881,G,02:18:45.27,-06:38:20.7,Cet -NGC0882,G,02:19:39.91,+15:48:51.3,Ari -NGC0883,G,02:19:05.18,-06:47:27.3,Cet -NGC0884,OCl,02:22:32.10,+57:08:38.8,Per -NGC0885,Dup,02:14:33.56,-00:46:00.1,Cet -NGC0886,OCl,02:23:11.77,+63:46:43.5,Cas -NGC0887,G,02:19:32.61,-16:04:11.0,Cet -NGC0888,G,02:17:27.10,-59:51:39.8,Hor -NGC0889,G,02:19:06.93,-41:44:57.6,Phe -NGC0890,G,02:22:01.01,+33:15:57.8,Tri -NGC0891,G,02:22:33.41,+42:20:56.9,And -NGC0892,G,02:20:52.02,-23:06:49.1,Cet -NGC0893,G,02:19:58.58,-41:24:11.3,Phe -NGC0894,Other,02:21:34.57,-05:30:36.8,Cet -NGC0895,G,02:21:36.47,-05:31:17.0,Cet -NGC0896,Neb,02:25:27.82,+62:01:09.7,Cas -NGC0897,G,02:21:06.37,-33:43:14.4,For -NGC0898,G,02:23:20.37,+41:57:05.1,And -NGC0899,G,02:21:53.14,-20:49:23.7,Cet -NGC0900,G,02:23:32.18,+26:30:41.5,Ari -NGC0901,G,02:23:34.09,+26:33:25.4,Ari -NGC0902,G,02:22:21.76,-16:40:44.6,Cet -NGC0903,G,02:24:00.88,+27:21:22.6,Ari -NGC0904,G,02:24:05.57,+27:20:32.6,Ari -NGC0905,G,02:22:43.57,-08:43:08.5,Cet -NGC0906,G,02:25:16.26,+42:05:23.6,And -NGC0907,G,02:23:01.91,-20:42:43.4,Cet -NGC0908,G,02:23:04.57,-21:14:01.9,Cet -NGC0909,G,02:25:22.79,+42:02:08.4,And -NGC0910,G,02:25:26.78,+41:49:27.4,And -NGC0911,G,02:25:42.40,+41:57:22.6,And -NGC0912,G,02:25:42.73,+41:46:38.8,And -NGC0913,G,02:25:44.64,+41:47:57.9,And -NGC0914,G,02:26:05.18,+42:08:38.7,And -NGC0915,G,02:25:45.58,+27:13:15.6,Ari -NGC0916,G,02:25:47.64,+27:14:33.1,Ari -NGC0917,G,02:26:07.69,+31:54:44.4,Tri -NGC0918,G,02:25:50.84,+18:29:46.5,Ari -NGC0919,G,02:26:16.67,+27:12:43.6,Ari -NGC0920,G,02:27:51.78,+45:56:49.4,And -NGC0921,G,02:26:33.42,-15:50:51.1,Cet -NGC0922,G,02:25:04.42,-24:47:17.4,For -NGC0923,G,02:27:34.64,+41:58:39.4,And -NGC0924,G,02:26:46.83,+20:29:51.0,Ari -NGC0925,G,02:27:16.88,+33:34:45.0,Tri -NGC0926,G,02:26:06.71,-00:19:55.0,Cet -NGC0927,G,02:26:37.31,+12:09:19.2,Ari -NGC0928,G,02:27:41.02,+27:13:16.3,Ari -NGC0929,G,02:27:18.22,-12:05:12.9,Cet -NGC0930,Other,02:27:51.44,+20:20:30.4,Ari -NGC0931,G,02:28:14.48,+31:18:42.0,Tri -NGC0932,G,02:27:54.69,+20:19:56.9,Ari -NGC0933,G,02:29:17.49,+45:54:40.7,And -NGC0934,G,02:27:32.93,-00:14:40.4,Cet -NGC0935,G,02:28:11.15,+19:35:56.8,Ari -NGC0936,G,02:27:37.46,-01:09:22.6,Cet -NGC0937,G,02:29:28.08,+42:14:59.9,And -NGC0938,G,02:28:33.51,+20:17:01.3,Ari -NGC0939,G,02:26:21.32,-44:26:46.2,Eri -NGC0940,G,02:29:27.50,+31:38:27.3,Tri -NGC0941,G,02:28:27.85,-01:09:05.5,Cet -NGC0942,G,02:29:10.25,-10:50:10.1,Cet -NGC0943,G,02:29:09.68,-10:49:41.0,Cet -NGC0944,G,02:26:41.53,-14:30:56.3,Cet -NGC0945,G,02:28:37.28,-10:32:20.3,Cet -NGC0946,G,02:30:38.43,+42:13:57.4,And -NGC0947,G,02:28:33.12,-19:02:31.6,Cet -NGC0948,G,02:28:45.47,-10:30:50.0,Cet -NGC0949,G,02:30:48.65,+37:08:12.4,Tri -NGC0950,G,02:29:11.77,-11:01:28.9,Cet -NGC0951,G,02:28:56.88,-22:20:58.0,Cet -NGC0952,Dup,02:29:27.50,+31:38:27.3,Tri -NGC0953,G,02:31:09.78,+29:35:19.5,Tri -NGC0954,G,02:28:51.63,-41:24:09.6,Eri -NGC0955,G,02:30:33.15,-01:06:30.3,Cet -NGC0956,OCl,02:32:30.90,+44:35:36.5,And -NGC0957,OCl,02:33:19.03,+57:34:10.9,Per -NGC0958,G,02:30:42.83,-02:56:20.4,Cet -NGC0959,G,02:32:23.94,+35:29:40.7,Tri -NGC0960,G,02:31:41.36,-09:18:01.6,Cet -NGC0961,G,02:41:02.49,-06:56:09.3,Cet -NGC0962,G,02:32:39.83,+28:04:11.8,Ari -NGC0963,G,02:30:31.28,-04:12:55.5,Cet -NGC0964,G,02:31:05.79,-36:02:04.8,For -NGC0965,G,02:32:25.10,-18:38:23.0,Cet -NGC0966,G,02:31:47.16,-19:52:54.1,Cet -NGC0967,G,02:32:12.71,-17:13:00.7,Cet -NGC0968,G,02:34:06.21,+34:28:47.6,Tri -NGC0969,G,02:34:08.20,+32:56:49.0,Tri -NGC0970,GPair,02:34:11.30,+32:58:34.0,Tri -NGC0970 NED01,G,02:34:10.74,+32:58:29.5,Tri -NGC0970 NED02,G,02:34:11.70,+32:58:38.2,Tri -NGC0971,*,02:34:16.05,+32:59:13.6,Tri -NGC0972,G,02:34:13.38,+29:18:40.6,Ari -NGC0973,G,02:34:20.11,+32:30:20.2,Tri -NGC0974,G,02:34:25.80,+32:57:16.2,Tri -NGC0975,G,02:33:22.75,+09:36:06.1,Cet -NGC0976,G,02:34:00.02,+20:58:36.4,Ari -NGC0977,G,02:33:03.43,-10:45:35.9,Cet -NGC0978,GPair,02:34:47.60,+32:50:37.0,Tri -NGC0978A,G,02:34:46.97,+32:50:46.3,Tri -NGC0978B,G,02:34:48.10,+32:50:29.0,Tri -NGC0979,G,02:31:38.79,-44:31:27.5,Eri -NGC0980,G,02:35:18.56,+40:55:35.4,And -NGC0981,G,02:32:59.89,-10:58:26.2,Cet -NGC0982,G,02:35:24.87,+40:52:11.0,And -NGC0983,G,02:38:55.64,+34:37:20.2,Tri -NGC0984,G,02:34:43.10,+23:24:46.8,Ari -NGC0985,G,02:34:37.77,-08:47:15.4,Cet -NGC0986,G,02:33:34.35,-39:02:42.2,For -NGC0986A,G,02:32:41.45,-39:17:46.4,For -NGC0987,G,02:36:49.61,+33:19:38.1,Tri -NGC0988,G,02:35:27.75,-09:21:22.3,Cet -NGC0989,G,02:33:46.05,-16:30:40.2,Cet -NGC0990,G,02:36:18.21,+11:38:31.5,Ari -NGC0991,G,02:35:32.68,-07:09:16.0,Cet -NGC0992,G,02:37:25.49,+21:06:03.0,Ari -NGC0993,G,02:36:46.06,+02:03:01.5,Cet -NGC0994,Dup,02:36:46.06,+02:03:01.5,Cet -NGC0995,G,02:38:32.04,+41:31:45.3,And -NGC0996,G,02:38:39.87,+41:38:51.1,And -NGC0997,GPair,02:37:14.50,+07:18:28.0,Cet -NGC0997 NED01,G,02:37:14.42,+07:18:35.2,Cet -NGC0997 NED02,G,02:37:14.50,+07:18:20.3,Cet -NGC0998,G,02:37:16.50,+07:20:08.9,Cet -NGC0999,G,02:38:47.46,+41:40:13.8,And -NGC1000,G,02:38:49.74,+41:27:34.9,And -NGC1001,G,02:39:12.65,+41:40:18.2,Per -NGC1002,Dup,02:38:55.64,+34:37:20.2,Tri -NGC1003,G,02:39:16.89,+40:52:20.3,Per -NGC1004,G,02:37:41.78,+01:58:31.1,Cet -NGC1005,G,02:39:29.20,+41:29:20.3,Per -NGC1006,G,02:37:34.86,-11:01:30.0,Cet -NGC1007,G,02:37:52.26,+02:09:21.7,Cet -NGC1008,G,02:37:55.29,+02:04:46.7,Cet -NGC1009,G,02:38:19.07,+02:18:36.1,Cet -NGC1010,Dup,02:37:34.86,-11:01:30.0,Cet -NGC1011,G,02:37:38.89,-11:00:20.0,Cet -NGC1012,G,02:39:14.91,+30:09:05.0,Ari -NGC1013,G,02:37:50.47,-11:30:26.1,Cet -NGC1014,**,02:38:00.85,-09:34:24.2,Cet -NGC1015,G,02:38:11.56,-01:19:07.3,Cet -NGC1016,G,02:38:19.56,+02:07:09.3,Cet -NGC1017,G,02:37:49.84,-11:00:37.0,Cet -NGC1018,G,02:38:10.36,-09:32:38.1,Cet -NGC1019,G,02:38:27.41,+01:54:27.8,Cet -NGC1020,G,02:38:44.34,+02:13:52.6,Cet -NGC1021,G,02:38:48.02,+02:13:02.7,Cet -NGC1022,G,02:38:32.71,-06:40:38.7,Cet -NGC1023,G,02:40:24.01,+39:03:47.8,Per -NGC1023A,G,02:40:37.7,+39:03:27,Per -NGC1024,G,02:39:11.96,+10:50:48.6,Ari -NGC1025,G,02:36:19.93,-54:51:51.0,Hor -NGC1026,G,02:39:19.22,+06:32:38.4,Cet -NGC1027,OCl,02:42:35.06,+61:35:39.7,Cas -NGC1028,G,02:39:37.15,+10:50:37.2,Ari -NGC1029,G,02:39:36.54,+10:47:36.0,Ari -NGC1030,G,02:39:50.60,+18:01:27.4,Ari -NGC1031,G,02:36:38.76,-54:51:35.2,Hor -NGC1032,G,02:39:23.64,+01:05:37.6,Cet -NGC1033,G,02:40:16.12,-08:46:37.0,Cet -NGC1034,G,02:38:14.02,-15:48:32.7,Cet -NGC1035,G,02:39:29.09,-08:07:58.6,Cet -NGC1036,G,02:40:28.99,+19:17:49.6,Ari -NGC1037,Other,02:39:58.38,-01:44:02.6,Cet -NGC1038,G,02:40:06.32,+01:30:31.6,Cet -NGC1039,OCl,02:42:07.40,+42:44:46.1,Per -NGC1040,G,02:43:12.44,+41:30:02.2,Per -NGC1041,G,02:40:25.22,-05:26:25.6,Cet -NGC1042,G,02:40:23.97,-08:26:00.8,Cet -NGC1043,G,02:40:46.57,+01:20:35.3,Cet -NGC1044,GPair,02:41:06.60,+08:44:14.0,Cet -NGC1044 NED01,G,02:41:06.16,+08:44:16.9,Cet -NGC1044 NED02,G,02:41:07.25,+08:44:10.2,Cet -NGC1045,G,02:40:29.12,-11:16:39.2,Cet -NGC1046,G,02:41:12.85,+08:43:09.8,Cet -NGC1047,G,02:40:32.84,-08:08:51.6,Cet -NGC1048,G,02:40:37.95,-08:32:00.1,Cet -NGC1048A,G,02:40:35.7,-08:32:50,Cet -NGC1049,GCl,02:39:48.14,-34:15:29.7,For -NGC1050,G,02:42:35.59,+34:45:48.6,Per -NGC1051,Dup,02:41:02.49,-06:56:09.3,Cet -NGC1052,G,02:41:04.80,-08:15:20.8,Cet -NGC1053,Dup,02:43:12.44,+41:30:02.2,Per -NGC1054,G,02:42:15.74,+18:13:01.9,Ari -NGC1055,G,02:41:45.23,+00:26:35.4,Cet -NGC1056,G,02:42:48.30,+28:34:27.1,Ari -NGC1057,G,02:43:02.90,+32:29:28.3,Tri -NGC1058,G,02:43:30.00,+37:20:28.8,Per -NGC1059,**,02:42:35.60,+17:59:48.3,Ari -NGC1060,G,02:43:15.05,+32:25:29.9,Tri -NGC1061,G,02:43:15.76,+32:28:00.2,Tri -NGC1062,*,02:43:24.02,+32:27:43.7,Tri -NGC1063,G,02:42:10.06,-05:34:06.8,Cet -NGC1064,G,02:42:23.53,-09:21:44.2,Cet -NGC1065,G,02:42:06.27,-15:05:29.6,Cet -NGC1066,G,02:43:49.94,+32:28:30.0,Tri -NGC1067,G,02:43:50.52,+32:30:42.8,Tri -NGC1068,G,02:42:40.71,-00:00:47.8,Cet -NGC1069,G,02:42:59.82,-08:17:22.2,Cet -NGC1070,G,02:43:22.27,+04:58:06.3,Cet -NGC1071,G,02:43:07.85,-08:46:26.0,Cet -NGC1072,G,02:43:31.31,+00:18:24.5,Cet -NGC1073,G,02:43:40.52,+01:22:34.0,Cet -NGC1074,G,02:43:36.12,-16:17:49.5,Cet -NGC1075,G,02:43:33.55,-16:12:04.0,Cet -NGC1076,G,02:43:29.25,-14:45:15.5,Cet -NGC1077,GPair,02:46:01.7,+40:05:30,Per -NGC1077A,G,02:46:02.9,+40:05:36,Per -NGC1077B,G,02:46:00.6,+40:05:25,Per -NGC1078,G,02:44:08.04,-09:27:08.6,Cet -NGC1079,G,02:43:44.34,-29:00:12.1,For -NGC1080,G,02:45:09.94,-04:42:38.8,Cet -NGC1081,G,02:45:05.52,-15:35:16.1,Eri -NGC1082,G,02:45:41.25,-08:10:49.9,Eri -NGC1083,G,02:45:40.60,-15:21:28.0,Eri -NGC1084,G,02:45:59.91,-07:34:42.5,Eri -NGC1085,G,02:46:25.30,+03:36:26.2,Cet -NGC1086,G,02:47:56.37,+41:14:47.3,Per -NGC1087,G,02:46:25.16,-00:29:55.1,Cet -NGC1088,GPair,02:47:04.40,+16:12:04.0,Ari -NGC1088 NED01,G,02:47:04.01,+16:11:59.7,Ari -NGC1088 NED02,G,02:47:04.89,+16:12:08.7,Ari -NGC1089,G,02:46:10.10,-15:04:23.5,Eri -NGC1090,G,02:46:33.94,-00:14:49.8,Cet -NGC1091,G,02:45:22.43,-17:31:59.4,Eri -NGC1092,G,02:45:29.57,-17:32:32.2,Eri -NGC1093,G,02:48:16.15,+34:25:11.2,Tri -NGC1094,G,02:47:27.83,-00:17:06.4,Cet -NGC1095,G,02:47:37.86,+04:38:15.5,Cet -NGC1096,G,02:43:49.29,-59:54:48.3,Hor -NGC1097,G,02:46:19.05,-30:16:29.6,For -NGC1097A,G,02:46:09.9,-30:13:41,For -NGC1098,G,02:44:53.67,-17:39:32.9,Eri -NGC1099,G,02:45:18.02,-17:42:30.1,Eri -NGC1100,G,02:45:36.07,-17:41:20.2,Eri -NGC1101,G,02:48:14.83,+04:34:40.9,Cet -NGC1102,G,02:47:12.90,-22:12:31.8,Eri -NGC1103,G,02:48:06.00,-13:57:33.2,Eri -NGC1104,G,02:48:38.69,-00:16:17.5,Cet -NGC1105,G,02:43:41.98,-15:42:20.1,Cet -NGC1106,G,02:50:40.51,+41:40:17.4,Per -NGC1107,G,02:49:19.61,+08:05:33.5,Cet -NGC1108,G,02:48:38.54,-07:57:04.0,Eri -NGC1109,G,02:47:43.58,+13:15:19.2,Ari -NGC1110,G,02:49:09.57,-07:50:15.2,Eri -NGC1111,G,02:48:39.35,+13:15:34.3,Ari -NGC1112,G,02:49:00.37,+13:13:25.5,Ari -NGC1113,*,02:50:05.05,+13:19:38.7,Ari -NGC1114,G,02:49:07.20,-16:59:36.1,Eri -NGC1115,G,02:50:25.38,+13:15:58.4,Ari -NGC1116,G,02:50:35.69,+13:20:06.2,Ari -NGC1117,G,02:51:13.11,+13:11:07.1,Ari -NGC1118,G,02:49:58.67,-12:09:49.4,Eri -NGC1119,G,02:48:17.08,-17:59:15.4,Eri -NGC1120,G,02:49:04.09,-14:28:14.5,Eri -NGC1121,G,02:50:39.19,-01:44:02.6,Eri -NGC1122,G,02:52:51.20,+42:12:18.1,Per -NGC1123,Dup,02:52:51.20,+42:12:18.1,Per -NGC1124,G,02:51:35.92,-25:42:06.6,For -NGC1125,G,02:51:40.27,-16:39:03.7,Eri -NGC1126,G,02:52:18.63,-01:17:45.6,Eri -NGC1127,G,02:52:51.84,+13:15:23.1,Ari -NGC1128,GPair,02:57:41.57,+06:01:28.8,Cet -NGC1128 NED01,G,02:57:41.65,+06:01:21.9,Cet -NGC1128 NED02,G,02:57:41.56,+06:01:36.9,Cet -NGC1129,G,02:54:27.38,+41:34:46.5,Per -NGC1130,G,02:54:24.38,+41:36:20.2,Per -NGC1131,G,02:54:33.98,+41:33:32.5,Per -NGC1132,G,02:52:51.91,-01:16:28.9,Eri -NGC1133,G,02:52:42.19,-08:48:15.8,Eri -NGC1134,G,02:53:41.34,+13:00:50.9,Ari -NGC1135,G,02:50:47.22,-54:55:46.5,Hor -NGC1136,G,02:50:53.70,-54:58:33.3,Hor -NGC1137,G,02:54:02.73,+02:57:43.5,Cet -NGC1138,G,02:56:36.44,+43:02:50.5,Per -NGC1139,G,02:52:46.81,-14:31:45.6,Eri -NGC1140,G,02:54:33.58,-10:01:39.9,Eri -NGC1141,Dup,02:55:09.71,-00:10:40.3,Cet -NGC1142,Dup,02:55:12.20,-00:11:00.8,Cet -NGC1143,G,02:55:09.71,-00:10:40.3,Cet -NGC1144,G,02:55:12.20,-00:11:00.8,Cet -NGC1145,G,02:54:33.51,-18:38:06.3,Eri -NGC1146,Other,02:57:37.34,+46:25:37.0,Per -NGC1147,Other,02:55:09.29,-09:07:10.7,Eri -NGC1148,G,02:57:04.37,-07:41:08.3,Eri -NGC1149,G,02:57:23.87,-00:18:33.9,Cet -NGC1150,G,02:57:01.39,-15:02:54.3,Eri -NGC1151,G,02:57:04.63,-15:00:46.9,Eri -NGC1152,G,02:57:33.63,-07:45:31.8,Eri -NGC1153,G,02:58:10.28,+03:21:42.9,Cet -NGC1154,G,02:58:07.67,-10:21:47.9,Eri -NGC1155,G,02:58:13.09,-10:21:02.1,Eri -NGC1156,G,02:59:42.30,+25:14:16.2,Ari -NGC1157,G,02:58:06.68,-15:07:06.7,Eri -NGC1158,G,02:57:11.46,-14:23:43.9,Eri -NGC1159,G,03:00:46.52,+43:09:45.2,Per -NGC1160,G,03:01:13.25,+44:57:19.5,Per -NGC1161,G,03:01:14.13,+44:53:50.4,Per -NGC1162,G,02:58:56.00,-12:23:54.9,Eri -NGC1163,G,03:00:22.09,-17:09:09.1,Eri -NGC1164,G,03:01:59.85,+42:35:05.8,Per -NGC1165,G,02:58:47.69,-32:05:57.1,For -NGC1166,G,03:00:35.00,+11:50:34.0,Ari -NGC1167,G,03:01:42.37,+35:12:20.7,Per -NGC1168,G,03:00:47.02,+11:46:20.3,Ari -NGC1169,G,03:03:34.75,+46:23:10.9,Per -NGC1170,Other,03:02:26.89,+27:04:21.6,Ari -NGC1171,G,03:03:58.99,+43:23:53.9,Per -NGC1172,G,03:01:36.05,-14:50:11.7,Eri -NGC1173,Other,03:03:57.71,+42:23:01.4,Per -NGC1174,G,03:05:30.86,+42:50:07.8,Per -NGC1175,G,03:04:32.35,+42:20:21.5,Per -NGC1176,*,03:04:34.88,+42:23:36.4,Per -NGC1177,G,03:04:37.13,+42:21:46.2,Per -NGC1178,*,03:04:38.82,+42:18:48.5,Per -NGC1179,G,03:02:38.48,-18:53:52.0,Eri -NGC1180,G,03:01:51.04,-15:01:47.4,Eri -NGC1181,G,03:01:42.76,-15:03:08.4,Eri -NGC1182,G,03:03:28.44,-09:40:13.2,Eri -NGC1183,*,03:04:46.16,+42:22:08.2,Per -NGC1184,G,03:16:45.03,+80:47:36.0,Cep -NGC1185,G,03:02:59.48,-09:07:55.7,Eri -NGC1186,Dup,03:05:30.86,+42:50:07.8,Per -NGC1187,G,03:02:37.59,-22:52:01.8,Eri -NGC1188,G,03:03:43.37,-15:29:04.5,Eri -NGC1189,G,03:03:24.47,-15:37:24.5,Eri -NGC1190,G,03:03:26.13,-15:39:42.8,Eri -NGC1191,G,03:03:30.89,-15:41:07.1,Eri -NGC1192,G,03:03:34.63,-15:40:43.8,Eri -NGC1193,OCl,03:05:55.67,+44:22:59.2,Per -NGC1194,G,03:03:49.11,-01:06:13.5,Cet -NGC1195,G,03:03:32.81,-12:02:23.2,Eri -NGC1196,G,03:03:35.20,-12:04:34.7,Eri -NGC1197,Other,03:06:14.21,+44:03:40.3,Per -NGC1198,G,03:06:13.24,+41:50:56.2,Per -NGC1199,G,03:03:38.41,-15:36:47.5,Eri -NGC1200,G,03:03:54.48,-11:59:30.5,Eri -NGC1201,G,03:04:07.98,-26:04:10.7,For -NGC1202,G,03:05:02.53,-06:29:29.8,Eri -NGC1203,GPair,03:05:14.20,-14:22:44.0,Eri -NGC1203A,G,03:05:14.1,-14:22:52,Eri -NGC1203B,G,03:05:14.3,-14:22:40,Eri -NGC1204,G,03:04:39.92,-12:20:29.0,Eri -NGC1205,Dup,03:03:28.44,-09:40:13.2,Eri -NGC1206,G,03:06:09.74,-08:49:59.4,Eri -NGC1207,G,03:08:15.49,+38:22:55.8,Per -NGC1208,G,03:06:11.91,-09:32:29.1,Eri -NGC1209,G,03:06:03.02,-15:36:40.5,Eri -NGC1210,G,03:06:45.36,-25:42:59.2,For -NGC1211,G,03:06:52.42,-00:47:40.1,Cet -NGC1212,G,03:09:42.23,+40:53:35.1,Per -NGC1213,G,03:09:17.31,+38:38:58.0,Per -NGC1214,G,03:06:56.02,-09:32:39.0,Eri -NGC1215,G,03:07:09.46,-09:35:33.6,Eri -NGC1216,G,03:07:18.54,-09:36:46.1,Eri -NGC1217,G,03:06:06.02,-39:02:11.0,For -NGC1218,G,03:08:26.22,+04:06:39.3,Cet -NGC1219,G,03:08:27.97,+02:06:30.9,Cet -NGC1220,OCl,03:11:40.67,+53:20:53.4,Per -NGC1221,G,03:08:15.53,-04:15:34.1,Eri -NGC1222,G,03:08:56.74,-02:57:18.5,Eri -NGC1223,G,03:08:19.93,-04:08:18.2,Eri -NGC1224,G,03:11:13.55,+41:21:49.3,Per -NGC1225,G,03:08:47.22,-04:06:05.6,Eri -NGC1226,G,03:11:05.35,+35:23:12.6,Per -NGC1227,G,03:11:07.73,+35:19:29.5,Per -NGC1228,G,03:08:11.74,-22:55:22.3,Eri -NGC1229,G,03:08:10.79,-22:57:38.9,Eri -NGC1230,G,03:08:16.40,-22:59:02.4,Eri -NGC1231,G,03:06:29.30,-15:34:08.7,Eri -NGC1232,G,03:09:45.51,-20:34:45.5,Eri -NGC1232A,G,03:10:01.9,-20:36:02,Eri -NGC1233,G,03:12:33.11,+39:19:08.1,Per -NGC1234,G,03:09:39.09,-07:50:46.3,Eri -NGC1235,Dup,03:12:33.11,+39:19:08.1,Per -NGC1236,G,03:11:27.99,+10:48:29.8,Ari -NGC1237,**,03:10:08.97,-08:41:32.4,Eri -NGC1238,G,03:10:52.71,-10:44:53.0,Eri -NGC1239,G,03:10:53.72,-02:33:11.3,Eri -NGC1240,**,03:13:26.68,+30:30:25.8,Ari -NGC1241,G,03:11:14.64,-08:55:19.7,Eri -NGC1242,G,03:11:19.32,-08:54:08.7,Eri -NGC1243,**,03:11:25.46,-08:56:43.0,Eri -NGC1244,G,03:06:31.09,-66:46:32.2,Hor -NGC1245,OCl,03:14:41.46,+47:14:19.3,Per -NGC1246,G,03:07:02.09,-66:56:19.1,Hor -NGC1247,G,03:12:14.32,-10:28:52.0,Eri -NGC1248,G,03:12:48.56,-05:13:28.9,Eri -NGC1249,G,03:10:01.23,-53:20:08.7,Hor -NGC1250,G,03:15:21.10,+41:21:19.5,Per -NGC1251,**,03:14:09.11,+01:27:23.5,Cet -NGC1252,OCl,03:10:44.30,-57:45:31.0,Hor -NGC1253,G,03:14:09.05,-02:49:22.6,Eri -NGC1253A,G,03:14:23.3,-02:48:03,Eri -NGC1254,G,03:14:24.05,+02:40:38.5,Cet -NGC1255,G,03:13:32.04,-25:43:30.6,For -NGC1256,G,03:13:58.27,-21:59:11.1,Eri -NGC1257,**,03:16:59.55,+41:31:44.6,Per -NGC1258,G,03:14:05.47,-21:46:27.4,Eri -NGC1259,G,03:17:17.28,+41:23:07.8,Per -NGC1260,G,03:17:27.24,+41:24:18.8,Per -NGC1261,GCl,03:12:15.34,-55:13:00.5,Hor -NGC1262,G,03:15:33.58,-15:52:45.5,Eri -NGC1263,G,03:15:39.56,-15:05:53.9,Eri -NGC1264,G,03:17:59.57,+41:31:13.0,Per -NGC1265,G,03:18:15.66,+41:51:27.9,Per -NGC1266,G,03:16:00.75,-02:25:38.5,Eri -NGC1267,G,03:18:44.92,+41:28:03.8,Per -NGC1268,G,03:18:45.16,+41:29:19.3,Per -NGC1269,G,03:17:18.59,-41:06:29.0,Eri -NGC1270,G,03:18:58.15,+41:28:12.0,Per -NGC1271,G,03:19:11.28,+41:21:11.7,Per -NGC1272,G,03:19:21.29,+41:29:26.3,Per -NGC1273,G,03:19:26.74,+41:32:25.9,Per -NGC1274,G,03:19:40.55,+41:32:55.1,Per -NGC1275,G,03:19:48.16,+41:30:42.1,Per -NGC1276,**,03:19:51.21,+41:38:31.1,Per -NGC1277,G,03:19:51.49,+41:34:24.7,Per -NGC1278,G,03:19:54.15,+41:33:48.2,Per -NGC1279,G,03:19:59.07,+41:28:46.3,Per -NGC1280,G,03:17:57.07,-00:10:08.7,Cet -NGC1281,G,03:20:06.11,+41:37:48.1,Per -NGC1282,G,03:20:12.12,+41:22:01.2,Per -NGC1283,G,03:20:15.52,+41:23:55.2,Per -NGC1284,G,03:17:45.51,-10:17:20.7,Eri -NGC1285,G,03:17:53.43,-07:17:52.1,Eri -NGC1286,G,03:17:48.53,-07:37:00.7,Eri -NGC1287,G,03:18:33.47,-02:43:51.0,Eri -NGC1288,G,03:17:13.19,-32:34:33.2,For -NGC1289,G,03:18:49.81,-01:58:24.0,Eri -NGC1290,G,03:19:25.17,-13:59:22.6,Eri -NGC1291,Dup,03:17:18.59,-41:06:29.0,Eri -NGC1292,G,03:18:14.89,-27:36:37.2,For -NGC1293,G,03:21:36.46,+41:23:34.2,Per -NGC1294,G,03:21:39.96,+41:21:38.0,Per -NGC1295,G,03:20:03.30,-13:59:53.7,Eri -NGC1296,G,03:18:49.71,-13:03:44.8,Eri -NGC1297,G,03:19:14.22,-19:06:00.0,Eri -NGC1298,G,03:20:13.08,-02:06:50.8,Eri -NGC1299,G,03:20:09.68,-06:15:43.2,Eri -NGC1300,G,03:19:41.08,-19:24:40.9,Eri -NGC1301,G,03:20:35.33,-18:42:55.2,Eri -NGC1302,G,03:19:51.18,-26:03:37.6,For -NGC1303,G,03:20:40.79,-07:23:39.9,Eri -NGC1304,G,03:21:12.78,-04:35:02.7,Eri -NGC1305,G,03:21:22.97,-02:19:00.6,Eri -NGC1306,G,03:21:02.99,-25:30:45.2,For -NGC1307,Dup,03:21:12.78,-04:35:02.7,Eri -NGC1308,G,03:22:28.54,-02:45:25.7,Eri -NGC1309,G,03:22:06.56,-15:24:00.2,Eri -NGC1310,G,03:21:03.43,-37:06:06.1,For -NGC1311,G,03:20:06.96,-52:11:07.9,Hor -NGC1312,**,03:23:41.73,+01:11:04.8,Tau -NGC1313,G,03:18:16.05,-66:29:53.7,Ret -NGC1313A,G,03:20:05.7,-66:42:04,Ret -NGC1314,G,03:22:41.16,-04:11:11.6,Eri -NGC1315,G,03:23:06.59,-21:22:30.6,Eri -NGC1316,G,03:22:41.72,-37:12:29.6,For -NGC1316A,G,03:23:37.9,-36:54:14,For -NGC1316B,G,03:23:39.6,-36:54:30,For -NGC1316C,G,03:24:58.4,-37:00:34,For -NGC1317,G,03:22:44.29,-37:06:13.3,For -NGC1318,Dup,03:22:44.29,-37:06:13.3,For -NGC1319,G,03:23:56.46,-21:31:39.8,Eri -NGC1320,G,03:24:48.70,-03:02:32.2,Eri -NGC1321,G,03:24:48.57,-03:00:56.1,Eri -NGC1322,G,03:24:54.71,-02:55:09.2,Eri -NGC1323,G,03:24:56.08,-02:49:19.5,Eri -NGC1324,G,03:25:01.68,-05:44:44.9,Eri -NGC1325,G,03:24:25.57,-21:32:38.5,Eri -NGC1325A,G,03:24:48.5,-21:20:10,Eri -NGC1326,G,03:23:56.40,-36:27:52.8,For -NGC1326A,G,03:25:08.5,-36:21:50,For -NGC1326B,G,03:25:20.3,-36:23:06,For -NGC1327,G,03:25:23.11,-25:40:49.0,For -NGC1328,G,03:25:39.10,-04:07:29.8,Eri -NGC1329,G,03:26:02.59,-17:35:29.4,Eri -NGC1330,Other,03:29:04.47,+41:40:30.9,Per -NGC1331,G,03:26:28.29,-21:21:19.1,Eri -NGC1332,G,03:26:17.25,-21:20:06.8,Eri -NGC1333,Cl+N,03:28:55.2,+31:22:12,Per -NGC1334,G,03:30:01.84,+41:49:55.3,Per -NGC1335,G,03:30:19.49,+41:34:21.9,Per -NGC1336,G,03:26:32.19,-35:42:48.8,For -NGC1337,G,03:28:05.96,-08:23:19.0,Eri -NGC1338,G,03:28:54.55,-12:09:12.1,Eri -NGC1339,G,03:28:06.58,-32:17:10.0,For -NGC1340,G,03:28:19.67,-31:04:05.4,For -NGC1341,G,03:27:58.42,-37:09:00.0,For -NGC1342,OCl,03:31:40.12,+37:22:45.8,Per -NGC1343,G,03:37:49.70,+72:34:16.8,Cas -NGC1344,Dup,03:28:19.67,-31:04:05.4,For -NGC1345,G,03:29:31.69,-17:46:42.2,Eri -NGC1346,G,03:30:13.27,-05:32:35.9,Eri -NGC1347,GPair,03:29:41.50,-22:17:06.0,Eri -NGC1347 NED01,G,03:29:41.80,-22:16:44.9,Eri -NGC1347 NED02,G,03:29:41.19,-22:17:27.6,Eri -NGC1348,OCl,03:34:08.49,+51:25:13.9,Per -NGC1349,G,03:31:27.50,+04:22:51.0,Tau -NGC1350,G,03:31:08.12,-33:37:43.1,For -NGC1351,G,03:30:34.98,-34:51:14.2,For -NGC1351A,G,03:28:48.7,-35:10:41,For -NGC1352,G,03:31:32.98,-19:16:42.4,Eri -NGC1353,G,03:32:03.02,-20:49:09.0,Eri -NGC1354,G,03:32:29.37,-15:13:16.1,Eri -NGC1355,G,03:33:23.50,-04:59:55.3,Eri -NGC1356,G,03:30:40.79,-50:18:34.6,Hor -NGC1357,G,03:33:17.08,-13:39:50.9,Eri -NGC1358,G,03:33:39.67,-05:05:21.8,Eri -NGC1359,G,03:33:47.71,-19:29:31.4,Eri -NGC1360,PN,03:33:14.65,-25:52:18.2,For -NGC1361,G,03:34:17.74,-06:15:54.0,Eri -NGC1362,G,03:33:53.08,-20:16:57.5,Eri -NGC1363,G,03:34:49.57,-09:50:32.9,Eri -NGC1364,G,03:34:58.98,-09:50:19.1,Eri -NGC1365,G,03:33:36.37,-36:08:25.4,For -NGC1366,G,03:33:53.68,-31:11:38.8,For -NGC1367,G,03:35:01.34,-24:55:59.6,For -NGC1368,G,03:34:58.90,-15:39:21.3,Eri -NGC1369,G,03:36:45.25,-36:15:22.4,Eri -NGC1370,G,03:35:14.57,-20:22:25.2,Eri -NGC1371,Dup,03:35:01.34,-24:55:59.6,For -NGC1372,G,03:36:59.74,-15:52:53.3,Eri -NGC1373,G,03:34:59.21,-35:10:16.0,For -NGC1374,G,03:35:16.59,-35:13:34.5,For -NGC1375,G,03:35:16.82,-35:15:56.4,For -NGC1376,G,03:37:05.92,-05:02:33.9,Eri -NGC1377,G,03:36:39.08,-20:54:08.1,Eri -NGC1378,**,03:35:58.20,-35:12:40.2,For -NGC1379,G,03:36:03.95,-35:26:28.3,For -NGC1380,G,03:36:27.59,-34:58:34.4,For -NGC1380A,G,03:36:47.5,-34:44:23,For -NGC1380B,Dup,03:37:08.96,-35:11:42.1,For -NGC1381,G,03:36:31.68,-35:17:42.7,For -NGC1382,G,03:37:08.96,-35:11:42.1,For -NGC1383,G,03:37:39.24,-18:20:22.1,Eri -NGC1384,G,03:39:13.59,+15:49:10.4,Tau -NGC1385,G,03:37:28.85,-24:30:01.1,For -NGC1386,G,03:36:46.18,-35:59:57.9,Eri -NGC1387,G,03:36:57.06,-35:30:23.9,For -NGC1388,G,03:38:12.01,-15:53:57.9,Eri -NGC1389,G,03:37:11.78,-35:44:44.1,Eri -NGC1390,G,03:37:52.17,-19:00:30.1,Eri -NGC1391,G,03:38:52.96,-18:21:14.8,Eri -NGC1392,G,03:37:47.01,-36:08:50.7,Eri -NGC1393,G,03:38:38.58,-18:25:40.7,Eri -NGC1394,G,03:39:06.92,-18:17:32.2,Eri -NGC1395,G,03:38:29.75,-23:01:39.1,Eri -NGC1396,G,03:38:06.54,-35:26:24.4,For -NGC1397,G,03:39:47.15,-04:40:12.4,Eri -NGC1398,G,03:38:52.13,-26:20:16.2,For -NGC1399,G,03:38:29.03,-35:27:02.4,For -NGC1400,G,03:39:30.84,-18:41:17.1,Eri -NGC1401,G,03:39:21.85,-22:43:28.9,Eri -NGC1402,G,03:39:30.57,-18:31:37.0,Eri -NGC1403,G,03:39:10.84,-22:23:19.5,Eri -NGC1404,G,03:38:51.92,-35:35:39.8,Eri -NGC1405,G,03:40:18.93,-15:31:48.7,Eri -NGC1406,G,03:39:23.30,-31:19:17.1,For -NGC1407,G,03:40:11.86,-18:34:48.4,Eri -NGC1408,**,03:39:17.28,-35:30:03.0,For -NGC1409,G,03:41:10.43,-01:18:09.2,Tau -NGC1410,G,03:41:10.75,-01:17:55.6,Tau -NGC1411,G,03:38:44.87,-44:06:02.2,Hor -NGC1412,G,03:40:29.37,-26:51:44.1,For -NGC1413,G,03:40:11.55,-15:36:38.8,Eri -NGC1414,G,03:40:57.04,-21:42:47.3,Eri -NGC1415,G,03:40:56.86,-22:33:52.1,Eri -NGC1416,G,03:41:02.89,-22:43:08.8,Eri -NGC1417,G,03:41:57.42,-04:42:17.5,Eri -NGC1418,G,03:42:16.16,-04:43:50.7,Eri -NGC1419,G,03:40:42.11,-37:30:39.0,Eri -NGC1420,Other,03:42:39.84,-05:51:09.2,Eri -NGC1421,G,03:42:29.28,-13:29:16.9,Eri -NGC1422,G,03:41:31.07,-21:40:53.5,Eri -NGC1423,G,03:42:40.10,-06:22:54.6,Eri -NGC1424,G,03:43:14.04,-04:43:48.3,Eri -NGC1425,G,03:42:11.47,-29:53:36.0,For -NGC1426,G,03:42:49.11,-22:06:30.1,Eri -NGC1427,G,03:42:19.42,-35:23:33.2,For -NGC1427A,G,03:40:09.3,-35:37:28,Eri -NGC1428,G,03:42:22.73,-35:09:14.4,For -NGC1429,Other,03:44:04.12,-04:43:05.2,Eri -NGC1430,*,03:43:25.22,-18:13:30.3,Eri -NGC1431,G,03:44:40.80,+02:50:05.8,Tau -NGC1432,HII,03:45:49.59,+24:22:04.3,Tau -NGC1433,G,03:42:01.55,-47:13:19.5,Hor -NGC1434,G,03:46:12.87,-09:40:57.4,Eri -NGC1435,Neb,03:46:10.09,+23:45:53.9,Tau -NGC1436,G,03:43:37.08,-35:51:10.9,Eri -NGC1437,Dup,03:43:37.08,-35:51:10.9,Eri -NGC1437B,G,03:45:54.8,-36:21:25,Eri -NGC1438,G,03:45:17.23,-23:00:08.9,Eri -NGC1439,G,03:44:49.95,-21:55:14.0,Eri -NGC1440,G,03:45:02.91,-18:15:57.7,Eri -NGC1441,G,03:45:43.12,-04:05:29.7,Eri -NGC1442,Dup,03:45:02.91,-18:15:57.7,Eri -NGC1443,*,03:45:53.04,-04:03:09.8,Eri -NGC1444,OCl,03:49:28.88,+52:39:19.2,Per -NGC1445,G,03:44:56.22,-09:51:21.1,Eri -NGC1446,*,03:45:57.46,-04:06:43.8,Eri -NGC1447,G,03:45:47.15,-09:01:07.3,Eri -NGC1448,G,03:44:31.92,-44:38:41.4,Hor -NGC1449,G,03:46:03.07,-04:08:17.2,Eri -NGC1450,G,03:45:36.64,-09:14:05.5,Eri -NGC1451,G,03:46:07.16,-04:04:09.1,Eri -NGC1452,G,03:45:22.31,-18:38:01.1,Eri -NGC1453,G,03:46:27.25,-03:58:07.6,Eri -NGC1454,*,03:45:59.34,-20:39:08.3,Eri -NGC1455,Dup,03:45:22.31,-18:38:01.1,Eri -NGC1456,**,03:48:08.25,+22:33:31.0,Tau -NGC1457,Dup,03:44:31.92,-44:38:41.4,Hor -NGC1458,Other,03:46:58.31,-18:14:28.1,Eri -NGC1459,G,03:46:57.94,-25:31:18.3,For -NGC1460,G,03:46:13.74,-36:41:46.8,Eri -NGC1461,G,03:48:27.14,-16:23:34.4,Eri -NGC1462,G,03:50:23.43,+06:58:23.0,Tau -NGC1463,G,03:46:15.42,-59:48:36.5,Ret -NGC1464,G,03:51:24.52,-15:24:08.1,Eri -NGC1465,G,03:53:31.91,+32:29:34.1,Per -NGC1466,GCl,03:44:33.35,-71:40:17.7,Hyi -NGC1467,G,03:51:52.80,-08:50:17.5,Eri -NGC1468,G,03:52:12.58,-06:20:56.2,Eri -NGC1469,G,04:00:27.77,+68:34:39.8,Cam -NGC1470,G,03:52:09.84,-08:59:57.7,Eri -NGC1471,Dup,03:51:24.52,-15:24:08.1,Eri -NGC1472,G,03:53:47.35,-08:34:06.5,Eri -NGC1473,G,03:47:26.30,-68:13:14.1,Hyi -NGC1474,G,03:54:30.35,+10:42:25.2,Tau -NGC1475,G,03:53:49.83,-08:08:15.0,Eri -NGC1476,G,03:52:08.79,-44:31:56.9,Hor -NGC1477,G,03:54:02.88,-08:34:29.9,Eri -NGC1478,G,03:54:07.34,-08:33:19.5,Eri -NGC1479,Other,03:54:20.44,-10:12:31.0,Eri -NGC1480,Other,03:54:32.38,-10:15:31.8,Eri -NGC1481,G,03:54:28.96,-20:25:37.8,Eri -NGC1482,G,03:54:38.96,-20:30:09.6,Eri -NGC1483,G,03:52:47.64,-47:28:39.1,Hor -NGC1484,G,03:54:20.13,-36:58:08.0,Eri -NGC1485,G,04:05:03.94,+70:59:47.5,Cam -NGC1486,G,03:56:18.70,-21:49:16.1,Eri -NGC1487,GPair,03:55:46.10,-42:22:05.0,Eri -NGC1487 NED01,G,03:55:45.37,-42:22:03.7,Eri -NGC1487 NED02,G,03:55:47.17,-42:22:06.9,Eri -NGC1488,**,04:00:04.33,+18:34:02.3,Tau -NGC1489,G,03:57:38.10,-19:12:59.9,Eri -NGC1490,G,03:53:34.22,-66:01:05.0,Ret -NGC1491,HII,04:03:13.56,+51:18:57.9,Per -NGC1492,G,03:58:13.13,-35:26:46.9,Eri -NGC1493,G,03:57:27.43,-46:12:38.5,Hor -NGC1494,G,03:57:42.90,-48:54:29.1,Hor -NGC1495,G,03:58:21.82,-44:27:58.5,Hor -NGC1496,OCl,04:04:31.89,+52:39:41.0,Per -NGC1497,G,04:02:06.82,+23:07:58.5,Tau -NGC1498,OCl,04:00:19.30,-12:01:11.0,Eri -NGC1499,Neb,04:03:14.42,+36:22:02.9,Per -NGC1500,G,03:58:13.97,-52:19:41.4,Dor -NGC1501,PN,04:06:59.37,+60:55:14.5,Cam -NGC1502,OCl,04:07:49.30,+62:19:53.5,Cam -NGC1503,G,03:56:33.24,-66:02:26.4,Ret -NGC1504,G,04:02:29.70,-09:20:07.4,Eri -NGC1505,G,04:02:36.40,-09:19:20.7,Eri -NGC1506,G,04:00:21.57,-52:34:25.4,Dor -NGC1507,G,04:04:27.21,-02:11:18.9,Eri -NGC1508,G,04:05:47.64,+25:24:30.5,Tau -NGC1509,G,04:03:55.20,-11:10:44.5,Eri -NGC1510,G,04:03:32.64,-43:24:00.4,Hor -NGC1511,G,03:59:36.98,-67:38:03.3,Hyi -NGC1511A,G,04:00:18.6,-67:48:25,Hyi -NGC1511B,G,04:00:54.7,-67:36:43,Hyi -NGC1512,G,04:03:54.28,-43:20:55.9,Hor -NGC1513,OCl,04:09:54.70,+49:31:02.2,Per -NGC1514,PN,04:09:16.95,+30:46:33.3,Tau -NGC1515,G,04:04:02.72,-54:06:00.2,Dor -NGC1515A,G,04:03:49.8,-54:06:47,Dor -NGC1516,GPair,04:08:07.7,-08:49:53,Eri -NGC1516A,G,04:08:07.4,-08:49:45,Eri -NGC1516B,G,04:08:08.31,-08:50:08.6,Eri -NGC1517,G,04:09:11.93,+08:38:55.7,Tau -NGC1518,G,04:06:49.72,-21:10:21.5,Eri -NGC1519,G,04:08:07.60,-17:11:34.4,Eri -NGC1520,OCl,03:57:30.97,-76:50:02.2,Men -NGC1521,G,04:08:18.93,-21:03:07.1,Eri -NGC1522,G,04:06:07.92,-52:40:06.3,Dor -NGC1523,Other,04:06:11.02,-54:05:17.9,Dor -NGC1524,Dup,04:08:07.4,-08:49:45,Eri -NGC1525,Dup,04:08:08.31,-08:50:08.6,Eri -NGC1526,G,04:05:12.30,-65:50:23.3,Ret -NGC1527,G,04:08:24.14,-47:53:49.3,Hor -NGC1528,OCl,04:15:18.87,+51:12:41.3,Per -NGC1529,G,04:07:19.88,-62:53:57.5,Ret -NGC1530,G,04:23:27.10,+75:17:44.1,Cam -NGC1530A,G,04:44:28.50,+75:38:23.1,Cam -NGC1531,G,04:11:59.32,-32:51:02.9,Eri -NGC1532,G,04:12:04.33,-32:52:27.2,Eri -NGC1533,G,04:09:51.84,-56:07:06.4,Dor -NGC1534,G,04:08:46.07,-62:47:51.3,Ret -NGC1535,PN,04:14:15.77,-12:44:21.8,Eri -NGC1536,G,04:10:59.86,-56:28:49.6,Ret -NGC1537,G,04:13:40.71,-31:38:43.5,Eri -NGC1538,G,04:14:56.08,-13:11:30.2,Eri -NGC1539,G,04:19:01.96,+26:49:39.0,Tau -NGC1540,G,04:15:10.66,-28:29:18.5,Eri -NGC1540A,G,04:15:10.01,-28:28:55.5,Eri -NGC1541,G,04:17:00.23,+00:50:06.7,Tau -NGC1542,G,04:17:14.18,+04:46:53.9,Tau -NGC1543,G,04:12:43.25,-57:44:16.7,Ret -NGC1544,G,05:02:36.15,+86:13:20.4,Cep -NGC1545,OCl,04:20:56.26,+50:15:19.2,Per -NGC1546,G,04:14:36.54,-56:03:38.9,Dor -NGC1547,G,04:17:12.41,-17:51:26.9,Eri -NGC1548,OCl,04:21:19.10,+36:54:58.8,Per -NGC1549,G,04:15:45.13,-55:35:32.1,Dor -NGC1550,G,04:19:37.93,+02:24:34.1,Tau -NGC1551,Dup,04:19:37.93,+02:24:34.1,Tau -NGC1552,G,04:20:17.68,-00:41:33.8,Eri -NGC1553,G,04:16:10.47,-55:46:48.5,Dor -NGC1554,*,04:21:43.55,+19:31:14.1,Tau -NGC1555,RfN,04:21:59.43,+19:32:06.6,Tau -NGC1556,G,04:17:44.83,-50:09:52.0,Dor -NGC1557,OCl,04:13:11.17,-70:25:29.8,Hyi -NGC1558,G,04:20:16.18,-45:01:53.3,Cae -NGC1559,G,04:17:35.77,-62:47:01.2,Ret -NGC1560,G,04:32:49.09,+71:52:59.2,Cam -NGC1561,G,04:23:01.08,-15:50:44.4,Eri -NGC1562,G,04:21:47.62,-15:45:19.6,Eri -NGC1563,G,04:22:53.95,-15:43:57.5,Eri -NGC1564,G,04:23:00.95,-15:44:19.9,Eri -NGC1565,G,04:23:23.45,-15:44:39.6,Eri -NGC1566,G,04:20:00.42,-54:56:16.1,Dor -NGC1567,G,04:21:08.75,-48:15:17.2,Cae -NGC1568,G,04:24:25.34,-00:44:46.4,Eri -NGC1569,G,04:30:49.06,+64:50:52.6,Cam -NGC1570,G,04:22:08.94,-43:37:46.4,Cae -NGC1571,Dup,04:22:08.94,-43:37:46.4,Cae -NGC1572,G,04:22:42.81,-40:36:03.3,Cae -NGC1573,G,04:35:03.99,+73:15:44.7,Cam -NGC1574,G,04:21:58.82,-56:58:29.1,Ret -NGC1575,G,04:26:20.55,-10:05:54.4,Eri -NGC1576,G,04:26:18.82,-03:37:15.9,Eri -NGC1577,Dup,04:26:20.55,-10:05:54.4,Eri -NGC1578,G,04:23:46.65,-51:35:58.0,Dor -NGC1579,Cl+N,04:30:13.80,+35:16:10.0,Per -NGC1580,G,04:28:18.48,-05:10:43.6,Eri -NGC1581,G,04:24:44.96,-54:56:31.2,Dor -NGC1582,OCl,04:31:46.79,+43:47:05.4,Per -NGC1583,G,04:28:20.73,-17:35:43.6,Eri -NGC1584,G,04:28:10.26,-17:31:24.1,Eri -NGC1585,G,04:27:33.01,-42:09:54.8,Cae -NGC1586,G,04:30:38.23,-00:18:15.0,Eri -NGC1587,G,04:30:39.94,+00:39:41.7,Tau -NGC1588,G,04:30:43.77,+00:39:53.0,Tau -NGC1589,G,04:30:45.44,+00:51:49.2,Tau -NGC1590,G,04:31:10.22,+07:37:51.2,Tau -NGC1591,G,04:29:30.55,-26:42:47.2,Eri -NGC1592,G,04:29:40.13,-27:24:30.7,Eri -NGC1593,G,04:32:06.12,+00:34:02.5,Tau -NGC1594,G,04:30:51.59,-05:47:53.8,Eri -NGC1595,G,04:28:21.76,-47:48:57.2,Cae -NGC1596,G,04:27:38.11,-55:01:40.1,Dor -NGC1597,G,04:31:13.46,-11:17:25.5,Eri -NGC1598,G,04:28:33.67,-47:46:57.2,Cae -NGC1599,G,04:31:38.74,-04:35:18.0,Eri -NGC1600,G,04:31:39.94,-05:05:10.5,Eri -NGC1601,G,04:31:41.73,-05:03:37.3,Eri -NGC1602,G,04:27:54.97,-55:03:27.8,Dor -NGC1603,G,04:31:49.95,-05:05:39.9,Eri -NGC1604,G,04:31:58.57,-05:22:11.8,Eri -NGC1605,OCl,04:34:52.28,+45:16:17.0,Per -NGC1606,G,04:32:03.34,-05:01:56.8,Eri -NGC1607,G,04:32:03.09,-04:27:35.8,Eri -NGC1608,Dup,04:32:06.12,+00:34:02.5,Tau -NGC1609,G,04:32:45.08,-04:22:20.7,Eri -NGC1610,G,04:34:13.87,-04:41:59.1,Eri -NGC1611,G,04:33:05.96,-04:17:50.8,Eri -NGC1612,G,04:33:13.13,-04:10:20.7,Eri -NGC1613,G,04:33:25.33,-04:15:55.5,Eri -NGC1614,G,04:33:59.85,-08:34:44.0,Eri -NGC1615,G,04:36:01.04,+19:57:01.2,Tau -NGC1616,G,04:32:41.73,-43:42:57.2,Cae -NGC1617,G,04:31:39.53,-54:36:08.2,Dor -NGC1618,G,04:36:06.60,-03:08:55.5,Eri -NGC1619,Dup,04:34:13.87,-04:41:59.1,Eri -NGC1620,G,04:36:37.35,-00:08:37.0,Eri -NGC1621,G,04:36:25.06,-04:59:14.3,Eri -NGC1622,G,04:36:36.64,-03:11:19.8,Eri -NGC1623,G,04:35:32.41,-13:33:23.2,Eri -NGC1624,Cl+N,04:40:36.50,+50:27:42.0,Per -NGC1625,G,04:37:06.26,-03:18:12.6,Eri -NGC1626,Dup,04:36:25.06,-04:59:14.3,Eri -NGC1627,G,04:37:38.00,-04:53:14.9,Eri -NGC1628,G,04:37:36.26,-04:42:53.4,Eri -NGC1629,GCl,04:29:36.91,-71:50:17.8,Hyi -NGC1630,G,04:37:15.48,-18:54:05.5,Eri -NGC1631,G,04:38:24.17,-20:38:59.5,Eri -NGC1632,G,04:39:58.58,-09:27:22.4,Eri -NGC1633,G,04:40:09.11,+07:20:58.0,Tau -NGC1634,G,04:40:09.78,+07:20:19.6,Tau -NGC1635,G,04:40:07.88,-00:32:51.1,Eri -NGC1636,G,04:40:40.20,-08:36:27.9,Eri -NGC1637,G,04:41:28.18,-02:51:28.7,Eri -NGC1638,G,04:41:36.51,-01:48:32.5,Eri -NGC1639,Other,04:40:52.21,-16:59:27.1,Eri -NGC1640,G,04:42:14.52,-20:26:05.2,Eri -NGC1641,OCl,04:35:34.92,-65:45:46.4,Ret -NGC1642,G,04:42:54.91,+00:37:06.9,Tau -NGC1643,G,04:43:43.95,-05:19:10.0,Eri -NGC1644,GCl,04:37:40.09,-66:11:56.7,Dor -NGC1645,G,04:44:06.38,-05:27:56.2,Eri -NGC1646,G,04:44:23.60,-08:31:58.0,Eri -NGC1647,OCl,04:45:55.57,+19:05:42.4,Tau -NGC1648,G,04:44:34.80,-08:28:43.9,Eri -NGC1649,GCl,04:38:22.85,-68:40:22.5,Dor -NGC1650,G,04:45:11.51,-15:52:12.1,Eri -NGC1651,GCl,04:37:32.27,-70:35:09.1,Men -NGC1652,Dup,04:38:22.85,-68:40:22.5,Dor -NGC1653,G,04:45:47.35,-02:23:34.2,Eri -NGC1654,G,04:45:48.45,-02:05:02.0,Eri -NGC1655,Other,04:47:11.90,+20:55:25.2,Tau -NGC1656,G,04:45:53.39,-05:08:12.2,Eri -NGC1657,G,04:46:07.26,-02:04:38.2,Eri -NGC1658,G,04:44:01.36,-41:27:48.3,Cae -NGC1659,G,04:46:29.95,-04:47:19.6,Eri -NGC1660,G,04:44:11.41,-41:29:51.3,Cae -NGC1661,G,04:47:07.64,-02:03:16.5,Ori -NGC1662,OCl,04:48:28.95,+10:55:49.4,Ori -NGC1663,OCl,04:49:23.54,+13:09:03.8,Ori -NGC1664,OCl,04:51:05.43,+43:40:34.2,Aur -NGC1665,G,04:48:17.07,-05:25:39.4,Eri -NGC1666,G,04:48:32.84,-06:34:11.9,Eri -NGC1667,G,04:48:37.14,-06:19:11.9,Eri -NGC1668,G,04:46:05.93,-44:44:00.1,Cae -NGC1669,G,04:42:59.90,-65:48:51.6,Dor -NGC1670,G,04:49:42.62,-02:45:37.8,Ori -NGC1671,G,04:49:34.03,+00:15:10.3,Ori -NGC1672,G,04:45:42.50,-59:14:49.9,Dor -NGC1673,OCl,04:42:39.64,-69:49:16.5,Men -NGC1674,Other,04:52:24.97,+23:54:27.6,Tau -NGC1675,Other,04:52:24.97,+23:54:27.6,Tau -NGC1676,OCl,04:43:54.17,-68:49:39.3,Dor -NGC1677,G,04:50:52.07,-04:53:33.8,Eri -NGC1678,G,04:51:35.44,-02:37:23.3,Ori -NGC1679,G,04:49:54.64,-31:57:52.8,Cae -NGC1680,G,04:48:34.01,-47:48:59.2,Pic -NGC1681,G,04:51:50.06,-05:48:11.6,Eri -NGC1682,G,04:52:19.79,-03:06:21.1,Ori -NGC1683,G,04:52:17.60,-03:01:28.7,Ori -NGC1684,G,04:52:31.15,-03:06:21.8,Ori -NGC1685,G,04:52:34.23,-02:56:57.6,Ori -NGC1686,G,04:52:54.66,-15:20:47.3,Eri -NGC1687,G,04:51:21.37,-33:56:20.5,Cae -NGC1688,G,04:48:23.79,-59:48:01.2,Dor -NGC1689,Dup,04:48:37.14,-06:19:11.9,Eri -NGC1690,G,04:54:19.26,+01:38:25.3,Ori -NGC1691,G,04:54:38.34,+03:16:04.7,Ori -NGC1692,G,04:55:23.73,-20:34:16.2,Eri -NGC1693,GCl,04:47:38.77,-69:20:37.1,Dor -NGC1694,G,04:55:16.82,-04:39:09.6,Eri -NGC1695,GCl,04:47:44.48,-69:22:25.5,Dor -NGC1696,OCl,04:48:29.97,-68:14:34.3,Dor -NGC1697,GCl,04:48:35.81,-68:33:30.4,Dor -NGC1698,GCl,04:49:05.05,-69:06:54.3,Dor -NGC1699,G,04:56:59.63,-04:45:24.7,Eri -NGC1700,G,04:56:56.31,-04:51:56.8,Eri -NGC1701,G,04:55:51.13,-29:53:00.4,Cae -NGC1702,OCl,04:49:27.68,-69:51:02.8,Men -NGC1703,G,04:52:52.15,-59:44:32.1,Dor -NGC1704,OCl,04:49:55.46,-69:45:22.7,Dor -NGC1705,G,04:54:13.50,-53:21:39.8,Pic -NGC1706,G,04:52:31.02,-62:59:08.8,Dor -NGC1707,Other,04:58:20.61,+08:14:18.1,Ori -NGC1708,OCl,05:03:21.88,+52:49:55.5,Cam -NGC1709,G,04:58:44.03,-00:28:41.7,Ori -NGC1710,G,04:57:17.07,-15:17:20.4,Lep -NGC1711,GCl,04:50:36.13,-69:59:07.6,Men -NGC1712,*Ass,04:50:58.43,-69:24:27.0,Dor -NGC1713,G,04:58:54.61,-00:29:20.1,Ori -NGC1714,Cl+N,04:52:08.85,-66:55:24.2,Dor -NGC1715,EmN,04:52:10.48,-66:54:31.3,Dor -NGC1716,G,04:58:13.32,-20:21:48.9,Lep -NGC1717,G,04:58:31.46,-00:34:28.8,Ori -NGC1718,OCl,04:52:25.39,-67:03:02.4,Dor -NGC1719,G,04:59:34.63,-00:15:37.5,Ori -NGC1720,G,04:59:20.63,-07:51:32.3,Eri -NGC1721,G,04:59:17.40,-11:07:07.4,Eri -NGC1722,Cl+N,04:52:00.60,-69:22:30.0,Dor -NGC1723,G,04:59:25.89,-10:58:50.5,Eri -NGC1724,OCl,05:03:32.32,+49:29:30.4,Aur -NGC1725,G,04:59:22.89,-11:07:56.3,Eri -NGC1726,G,04:59:41.90,-07:45:18.9,Eri -NGC1727,Cl+N,04:52:12.77,-69:20:20.1,Dor -NGC1728,G,04:59:27.73,-11:07:22.6,Eri -NGC1729,G,05:00:15.69,-03:21:09.0,Ori -NGC1730,G,04:59:31.83,-15:49:25.1,Lep -NGC1731,*Ass,04:53:32.09,-66:55:31.0,Dor -NGC1732,OCl,04:53:10.59,-68:39:00.0,Dor -NGC1733,OCl,04:54:04.75,-66:40:57.3,Dor -NGC1734,OCl,04:53:33.49,-68:46:07.6,Dor -NGC1735,OCl,04:54:19.66,-67:05:58.4,Dor -NGC1736,EmN,04:53:01.55,-68:03:11.2,Dor -NGC1737,HII,04:53:59.71,-69:10:28.4,Dor -NGC1738,G,05:01:46.70,-18:09:25.4,Lep -NGC1739,G,05:01:47.34,-18:10:00.5,Lep -NGC1740,G,05:01:54.80,-03:17:46.8,Ori -NGC1741,G,05:01:38.30,-04:15:25.2,Eri -NGC1741B,G,05:01:36.2,-04:15:43,Eri -NGC1742,*,05:01:59.33,-03:17:41.9,Ori -NGC1743,Neb,04:54:02.67,-69:11:55.0,Dor -NGC1744,G,04:59:57.80,-26:01:20.0,Lep -NGC1745,Neb,04:54:20.62,-69:09:32.0,Dor -NGC1746,OCl,05:03:50.19,+23:46:03.5,Tau -NGC1747,OCl,04:55:10.98,-67:10:08.0,Dor -NGC1748,Neb,04:54:25.97,-69:11:02.8,Dor -NGC1749,OCl,04:54:56.06,-68:11:19.2,Dor -NGC1750,OCl,05:04:00.03,+23:38:44.8,Tau -NGC1751,GCl,04:54:12.58,-69:48:21.0,Men -NGC1752,G,05:02:09.49,-08:14:27.1,Eri -NGC1753,G,05:02:32.34,-03:20:39.6,Ori -NGC1754,GCl,04:54:18.95,-70:26:32.9,Men -NGC1755,GCl,04:55:14.87,-68:12:14.6,Dor -NGC1756,GCl,04:54:49.83,-69:14:15.5,Dor -NGC1757,Other,05:02:39.36,-04:43:22.6,Eri -NGC1758,OCl,05:04:42.24,+23:46:53.9,Tau -NGC1759,G,05:00:49.04,-38:40:26.0,Cae -NGC1760,HII,04:56:44.37,-66:31:38.4,Dor -NGC1761,*Ass,04:56:37.73,-66:28:43.9,Dor -NGC1762,G,05:03:37.03,+01:34:24.0,Ori -NGC1763,Cl+N,04:56:49.19,-66:24:32.7,Dor -NGC1764,OCl,04:56:27.72,-67:41:37.5,Dor -NGC1765,G,04:58:24.16,-62:01:42.2,Dor -NGC1766,OCl,04:55:57.60,-70:13:30.2,Men -NGC1767,OCl,04:56:27.30,-69:24:02.0,Dor -NGC1768,OCl,04:57:00.16,-68:14:58.0,Dor -NGC1769,EmN,04:57:47.37,-66:28:08.7,Dor -NGC1770,Cl+N,04:57:15.71,-68:25:05.1,Dor -NGC1771,G,04:58:55.72,-63:17:53.6,Dor -NGC1772,OCl,04:56:52.79,-69:33:21.8,Dor -NGC1773,Neb,04:58:12.03,-66:21:36.4,Dor -NGC1774,OCl,04:58:06.88,-67:14:32.4,Dor -NGC1775,OCl,04:56:54.78,-70:25:55.2,Men -NGC1776,OCl,04:58:39.72,-66:25:46.5,Dor -NGC1777,GCl,04:55:47.66,-74:17:07.3,Men -NGC1778,OCl,05:08:05.70,+37:01:22.2,Aur -NGC1779,G,05:05:18.07,-09:08:49.7,Eri -NGC1780,G,05:06:20.71,-19:28:00.0,Lep -NGC1781,G,05:07:55.03,-18:11:23.7,Lep -NGC1782,OCl,04:57:51.09,-69:23:31.9,Dor -NGC1783,GCl,04:59:08.81,-65:59:14.2,Dor -NGC1784,G,05:05:27.10,-11:52:17.5,Lep -NGC1785,*Ass,04:58:44.96,-68:49:29.5,Dor -NGC1786,GCl,04:59:07.82,-67:44:42.8,Dor -NGC1787,OCl,05:01:44.41,-65:49:23.9,Dor -NGC1788,RfN,05:06:53.22,-03:20:27.5,Ori -NGC1789,GCl,04:57:51.68,-71:54:07.1,Men -NGC1790,OCl,05:10:56.22,+52:03:35.3,Aur -NGC1791,OCl,04:59:06.48,-70:10:07.5,Men -NGC1792,G,05:05:14.45,-37:58:50.7,Col -NGC1793,OCl,04:59:38.21,-69:33:27.5,Dor -NGC1794,Dup,05:07:55.03,-18:11:23.7,Lep -NGC1795,OCl,04:59:46.90,-69:48:04.2,Dor -NGC1796,G,05:02:42.55,-61:08:24.2,Dor -NGC1796A,G,05:05:03.0,-61:29:04,Dor -NGC1796B,G,05:07:54.9,-61:11:28,Dor -NGC1797,G,05:07:44.88,-08:01:08.7,Eri -NGC1798,OCl,05:11:39.32,+47:41:43.8,Aur -NGC1799,G,05:07:44.62,-07:58:09.4,Eri -NGC1800,G,05:06:25.72,-31:57:15.2,Col -NGC1801,GCl,05:00:34.51,-69:36:49.5,Dor -NGC1802,OCl,05:10:12.87,+24:07:30.5,Tau -NGC1803,G,05:05:26.58,-49:34:04.6,Pic -NGC1804,OCl,05:01:03.26,-69:04:57.3,Dor -NGC1805,GCl,05:02:21.32,-66:06:44.1,Dor -NGC1806,GCl,05:02:12.37,-67:59:08.7,Dor -NGC1807,OCl,05:10:45.03,+16:30:45.9,Tau -NGC1808,G,05:07:42.34,-37:30:47.0,Col -NGC1809,G,05:02:05.01,-69:34:05.8,Dor -NGC1810,OCl,05:03:23.27,-66:22:54.5,Dor -NGC1811,G,05:08:42.74,-29:16:33.5,Col -NGC1812,G,05:08:52.91,-29:15:04.5,Col -NGC1813,OCl,05:02:40.28,-70:19:04.6,Men -NGC1814,Cl+N,05:03:46.46,-67:18:02.4,Dor -NGC1815,OCl,05:02:27.25,-70:37:15.8,Men -NGC1816,OCl,05:03:50.75,-67:15:38.7,Dor -NGC1817,OCl,05:12:26.27,+16:41:02.7,Tau -NGC1818,GCl,05:04:14.76,-66:26:04.2,Dor -NGC1819,G,05:11:46.14,+05:12:02.2,Ori -NGC1820,OCl,05:04:01.68,-67:15:57.4,Dor -NGC1821,G,05:11:46.10,-15:08:04.7,Lep -NGC1822,OCl,05:05:09.20,-66:12:38.0,Dor -NGC1823,OCl,05:03:24.96,-70:20:07.8,Men -NGC1824,G,05:06:56.20,-59:43:26.3,Dor -NGC1825,OCl,05:04:19.04,-68:55:35.1,Dor -NGC1826,OCl,05:05:33.29,-66:13:47.1,Dor -NGC1827,G,05:10:04.60,-36:57:36.9,Col -NGC1828,OCl,05:04:20.88,-69:23:17.4,Dor -NGC1829,Cl+N,05:05:00.00,-68:03:33.3,Dor -NGC1830,OCl,05:04:38.27,-69:20:24.6,Dor -NGC1831,GCl,05:06:16.72,-64:55:03.0,Dor -NGC1832,G,05:12:03.33,-15:41:16.1,Lep -NGC1833,Cl+N,05:04:24.51,-70:43:41.0,Men -NGC1834,OCl,05:05:11.38,-69:12:26.9,Dor -NGC1835,GCl,05:05:06.58,-69:24:13.9,Dor -NGC1836,OCl,05:05:34.42,-68:37:40.4,Dor -NGC1837,OCl,05:04:55.80,-70:42:50.4,Men -NGC1838,OCl,05:06:07.88,-68:26:42.7,Dor -NGC1839,OCl,05:06:02.35,-68:37:36.4,Dor -NGC1840,OCl,05:05:19.16,-71:45:46.4,Men -NGC1841,GCl,04:45:23.35,-83:59:56.6,Men -NGC1842,OCl,05:07:18.10,-67:16:23.4,Dor -NGC1843,G,05:14:06.02,-10:37:37.3,Ori -NGC1844,OCl,05:07:30.68,-67:19:24.3,Dor -NGC1845,*Ass,05:05:45.01,-70:34:53.8,Men -NGC1846,GCl,05:07:33.94,-67:27:41.3,Dor -NGC1847,OCl,05:07:08.16,-68:58:17.2,Dor -NGC1848,OCl,05:07:27.16,-71:11:43.3,Men -NGC1849,OCl,05:09:34.81,-66:18:56.9,Dor -NGC1850,GCl,05:08:44.73,-68:45:42.0,Dor -NGC1851,GCl,05:14:06.73,-40:02:47.8,Col -NGC1852,GCl,05:09:23.79,-67:46:34.5,Dor -NGC1853,G,05:12:16.52,-57:23:56.5,Dor -NGC1854,GCl,05:09:19.89,-68:50:50.5,Dor -NGC1855,*Ass,05:09:16.91,-68:50:44.3,Dor -NGC1856,GCl,05:09:29.37,-69:07:39.3,Dor -NGC1857,OCl,05:20:05.56,+39:20:37.0,Aur -NGC1858,Cl+N,05:09:51.94,-68:53:28.5,Dor -NGC1859,OCl,05:11:31.79,-65:14:59.0,Dor -NGC1860,OCl,05:10:39.51,-68:45:08.1,Dor -NGC1861,OCl,05:10:22.14,-70:46:37.6,Men -NGC1862,OCl,05:12:34.54,-66:09:15.7,Dor -NGC1863,OCl,05:11:39.58,-68:43:36.4,Dor -NGC1864,OCl,05:12:40.60,-67:37:16.5,Dor -NGC1865,OCl,05:12:25.08,-68:46:15.7,Dor -NGC1866,GCl,05:13:39.10,-65:27:56.1,Dor -NGC1867,OCl,05:13:42.26,-66:17:31.0,Dor -NGC1868,GCl,05:14:36.30,-63:57:16.2,Dor -NGC1869,Cl+N,05:13:56.32,-67:22:45.8,Dor -NGC1870,GCl,05:13:09.90,-69:07:01.0,Dor -NGC1871,Cl+N,05:13:51.76,-67:27:09.5,Dor -NGC1872,GCl,05:13:10.77,-69:18:41.7,Dor -NGC1873,Cl+N,05:13:55.67,-67:20:03.7,Dor -NGC1874,Cl+N,05:13:11.66,-69:22:34.4,Dor -NGC1875,G,05:21:45.76,+06:41:19.9,Ori -NGC1876,Cl+N,05:13:18.63,-69:21:43.7,Dor -NGC1877,Neb,05:13:38.84,-69:23:01.6,Dor -NGC1878,OCl,05:12:50.91,-70:28:18.1,Men -NGC1879,G,05:19:48.23,-32:08:33.9,Col -NGC1880,HII,05:13:25.18,-69:22:46.0,Dor -NGC1881,OCl,05:13:37.17,-69:17:57.0,Dor -NGC1882,OCl,05:15:33.33,-66:07:46.4,Dor -NGC1883,OCl,05:25:54.20,+46:29:24.5,Aur -NGC1884,Other,05:15:58.04,-66:09:48.2,Dor -NGC1885,OCl,05:15:05.86,-68:58:39.2,Dor -NGC1886,G,05:21:48.15,-23:48:36.5,Lep -NGC1887,OCl,05:16:05.93,-66:19:06.8,Dor -NGC1888,G,05:22:34.45,-11:29:58.3,Lep -NGC1889,G,05:22:35.30,-11:29:49.0,Lep -NGC1890,OCl,05:13:45.83,-72:04:40.7,Men -NGC1891,OCl,05:21:44.22,-35:47:21.5,Col -NGC1892,G,05:17:09.05,-64:57:35.1,Dor -NGC1893,OCl,05:22:44.14,+33:24:43.3,Aur -NGC1894,OCl,05:15:51.27,-69:28:06.7,Dor -NGC1895,Neb,05:16:51.59,-67:19:46.5,Dor -NGC1896,OCl,05:25:34.68,+29:15:36.8,Aur -NGC1897,OCl,05:17:31.28,-67:27:03.6,Dor -NGC1898,OCl,05:16:42.38,-69:39:22.4,Dor -NGC1899,HII,05:17:48.67,-67:54:02.6,Dor -NGC1900,OCl,05:19:09.38,-63:01:25.3,Dor -NGC1901,OCl,05:18:15.16,-68:26:10.7,Dor -NGC1902,OCl,05:18:19.14,-66:37:38.7,Dor -NGC1903,GCl,05:17:22.30,-69:20:07.1,Dor -NGC1904,GCl,05:24:10.59,-24:31:27.2,Lep -NGC1905,OCl,05:18:23.71,-67:16:41.4,Dor -NGC1906,G,05:24:47.10,-15:56:35.9,Lep -NGC1907,OCl,05:28:04.55,+35:19:32.4,Aur -NGC1908,Other,05:25:53.80,-02:31:44.0,Ori -NGC1909,RfN,05:04:55.44,-07:15:56.3,Eri -NGC1910,Cl+N,05:18:43.07,-69:13:54.9,Dor -NGC1911,OCl,05:19:26.88,-66:41:03.3,Dor -NGC1912,OCl,05:28:42.49,+35:51:17.7,Aur -NGC1913,OCl,05:18:19.29,-69:32:11.3,Dor -NGC1914,Cl+N,05:17:39.76,-71:15:21.1,Men -NGC1915,*Ass,05:19:42.24,-66:49:17.5,Dor -NGC1916,GCl,05:18:36.47,-69:24:24.5,Dor -NGC1917,GCl,05:19:01.97,-68:59:56.3,Dor -NGC1918,SNR,05:19:06.99,-69:39:44.8,Dor -NGC1919,Neb,05:20:19.05,-66:53:28.1,Dor -NGC1920,HII,05:20:33.50,-66:46:43.4,Dor -NGC1921,OCl,05:19:22.75,-69:47:15.9,Dor -NGC1922,OCl,05:19:48.92,-69:26:53.7,Dor -NGC1923,Cl+N,05:21:34.06,-65:29:12.2,Dor -NGC1924,G,05:28:01.93,-05:18:38.7,Ori -NGC1925,*Ass,05:21:43.96,-65:47:37.0,Dor -NGC1926,GCl,05:20:35.43,-69:31:31.0,Dor -NGC1927,Other,05:28:42.98,-08:22:38.4,Ori -NGC1928,OCl,05:20:56.50,-69:28:40.6,Dor -NGC1929,OCl,05:21:36.03,-67:54:43.4,Dor -NGC1930,G,05:25:56.80,-46:43:42.5,Pic -NGC1931,Cl+N,05:31:25.35,+34:14:47.6,Aur -NGC1932,*,05:22:17.24,-66:09:15.6,Dor -NGC1933,OCl,05:22:27.32,-66:09:07.8,Dor -NGC1934,Cl+N,05:21:47.89,-67:56:13.8,Dor -NGC1935,HII,05:21:58.71,-67:57:26.6,Dor -NGC1936,EmN,05:22:13.96,-67:58:41.9,Dor -NGC1937,*Ass,05:22:29.16,-67:53:40.8,Dor -NGC1938,GCl,05:21:24.71,-69:56:21.8,Men -NGC1939,GCl,05:21:26.61,-69:56:59.1,Men -NGC1940,GCl,05:22:43.74,-67:11:11.6,Dor -NGC1941,Cl+N,05:23:07.70,-66:22:43.1,Dor -NGC1942,OCl,05:24:44.54,-63:56:31.6,Dor -NGC1943,GCl,05:22:28.74,-70:09:17.5,Men -NGC1944,GCl,05:21:57.43,-72:29:38.7,Men -NGC1945,EmN,05:24:54.96,-66:27:26.9,Dor -NGC1946,OCl,05:25:16.38,-66:23:40.4,Dor -NGC1947,G,05:26:47.61,-63:45:36.1,Dor -NGC1948,Cl+N,05:25:46.24,-66:16:00.5,Dor -NGC1949,Neb,05:25:04.83,-68:28:21.1,Dor -NGC1950,OCl,05:24:32.99,-69:54:08.3,Men -NGC1951,OCl,05:26:06.82,-66:35:50.1,Dor -NGC1952,SNR,05:34:31.97,+22:00:52.1,Tau -NGC1953,GCl,05:25:27.81,-68:50:17.9,Dor -NGC1954,G,05:32:48.33,-14:03:46.0,Lep -NGC1955,HII,05:26:09.96,-67:29:50.6,Dor -NGC1956,G,05:19:35.32,-77:43:43.2,Men -NGC1957,G,05:32:55.20,-14:07:59.3,Lep -NGC1958,GCl,05:25:30.52,-69:50:12.4,Dor -NGC1959,OCl,05:25:36.63,-69:55:36.9,Men -NGC1960,OCl,05:36:17.74,+34:08:26.7,Aur -NGC1961,G,05:42:04.65,+69:22:42.4,Cam -NGC1962,OCl,05:26:17.73,-68:50:15.5,Dor -NGC1963,OCl,05:32:16.83,-36:23:55.1,Col -NGC1964,G,05:33:21.76,-21:56:44.8,Lep -NGC1965,OCl,05:26:31.26,-68:48:19.7,Dor -NGC1966,OCl,05:26:45.84,-68:49:11.5,Dor -NGC1967,OCl,05:26:43.31,-69:06:05.5,Dor -NGC1968,*Ass,05:27:22.11,-67:27:49.8,Dor -NGC1969,OCl,05:26:32.36,-69:50:28.9,Dor -NGC1970,OCl,05:26:52.67,-68:50:12.0,Dor -NGC1971,OCl,05:26:45.24,-69:51:05.8,Dor -NGC1972,OCl,05:26:47.36,-69:50:18.0,Dor -NGC1973,Neb,05:35:04.78,-04:43:54.4,Ori -NGC1974,OCl,05:27:59.02,-67:25:26.9,Dor -NGC1975,Neb,05:35:17.88,-04:41:06.8,Ori -NGC1976,Cl+N,05:35:16.48,-05:23:22.8,Ori -NGC1977,Cl+N,05:35:15.80,-04:50:39.6,Ori -NGC1978,GCl,05:28:45.13,-66:14:09.3,Dor -NGC1979,G,05:34:01.11,-23:18:36.3,Lep -NGC1980,Cl+N,05:35:25.99,-05:54:35.6,Ori -NGC1981,Cl+N,05:35:09.59,-04:25:30.2,Ori -NGC1982,HII,05:35:31.38,-05:16:02.9,Ori -NGC1983,OCl,05:27:44.25,-68:59:09.8,Dor -NGC1984,OCl,05:27:40.91,-69:08:03.6,Dor -NGC1985,RfN,05:37:47.81,+31:59:19.8,Aur -NGC1986,GCl,05:27:38.08,-69:58:25.1,Men -NGC1987,GCl,05:27:17.15,-70:44:14.5,Men -NGC1988,*,05:37:26.49,+21:13:05.7,Tau -NGC1989,G,05:34:23.42,-30:48:03.8,Col -NGC1990,*,05:36:12.82,-01:12:06.9,Ori -NGC1991,Dup,05:27:59.02,-67:25:26.9,Dor -NGC1992,G,05:34:31.77,-30:53:49.2,Col -NGC1993,G,05:35:25.57,-17:48:55.1,Lep -NGC1994,OCl,05:28:21.77,-69:08:30.6,Dor -NGC1995,**,05:33:03.34,-48:40:30.5,Pic -NGC1996,OCl,05:38:18.88,+25:49:23.4,Tau -NGC1997,OCl,05:30:34.70,-63:11:58.3,Dor -NGC1998,G,05:33:15.76,-48:41:44.2,Pic -NGC1999,RfN,05:36:25.35,-06:42:57.1,Ori -NGC2000,OCl,05:27:29.24,-71:52:45.8,Men -NGC2001,*Ass,05:29:02.06,-68:46:09.4,Dor -NGC2002,GCl,05:30:20.70,-66:53:06.0,Dor -NGC2003,GCl,05:30:55.20,-66:27:59.6,Dor -NGC2004,GCl,05:30:42.75,-67:17:11.2,Dor -NGC2005,GCl,05:30:10.85,-69:45:08.7,Dor -NGC2006,*Ass,05:31:21.00,-66:57:55.0,Dor -NGC2007,G,05:34:59.17,-50:55:18.1,Pic -NGC2008,G,05:35:03.81,-50:58:00.4,Pic -NGC2009,OCl,05:30:59.16,-69:10:54.0,Dor -NGC2010,OCl,05:30:34.93,-70:49:10.8,Men -NGC2011,OCl,05:32:20.21,-67:31:23.3,Dor -NGC2012,G,05:22:35.35,-79:51:06.5,Men -NGC2013,OCl,05:44:01.67,+55:47:36.9,Aur -NGC2014,Neb,05:32:19.87,-67:41:23.4,Dor -NGC2015,*Ass,05:32:06.49,-69:14:34.9,Dor -NGC2016,OCl,05:31:38.82,-69:56:45.1,Men -NGC2017,OCl,05:39:16.31,-17:50:54.6,Lep -NGC2018,HII,05:31:24.89,-71:04:08.6,Men -NGC2019,GCl,05:31:56.66,-70:09:34.5,Men -NGC2020,EmN,05:33:12.59,-67:42:57.2,Dor -NGC2021,OCl,05:33:30.67,-67:27:10.4,Dor -NGC2022,PN,05:42:06.21,+09:05:11.5,Ori -NGC2023,RfN,05:41:38.39,-02:15:32.5,Ori -NGC2024,Neb,05:41:42.57,-01:51:22.6,Ori -NGC2025,OCl,05:32:33.64,-71:42:55.8,Men -NGC2026,OCl,05:43:10.15,+20:08:20.0,Tau -NGC2027,OCl,05:34:59.71,-66:54:58.7,Dor -NGC2028,OCl,05:33:48.57,-69:57:06.5,Men -NGC2029,OCl,05:35:40.66,-66:02:06.0,Dor -NGC2030,HII,05:34:59.74,-67:33:22.9,Dor -NGC2031,GCl,05:33:41.82,-70:59:12.4,Men -NGC2032,HII,05:35:20.62,-67:34:06.4,Dor -NGC2033,*Ass,05:34:30.70,-69:46:48.5,Dor -NGC2034,*Ass,05:35:32.77,-66:54:13.1,Dor -NGC2035,Neb,05:35:31.20,-67:35:03.0,Dor -NGC2036,OCl,05:34:31.39,-70:03:51.7,Men -NGC2037,OCl,05:35:00.63,-69:43:53.7,Dor -NGC2038,OCl,05:34:42.27,-70:33:46.6,Men -NGC2039,Other,05:44:00.91,+08:41:27.8,Ori -NGC2040,Neb,05:36:05.93,-67:34:06.8,Dor -NGC2041,GCl,05:36:28.05,-66:59:23.2,Dor -NGC2042,OCl,05:36:09.58,-68:55:24.4,Dor -NGC2043,OCl,05:35:57.18,-70:04:27.9,Men -NGC2044,*Ass,05:36:06.18,-69:11:55.2,Dor -NGC2045,*,05:45:01.30,+12:53:18.1,Tau -NGC2046,OCl,05:35:37.53,-70:14:26.5,Men -NGC2047,OCl,05:35:52.99,-70:11:33.6,Men -NGC2048,EmN,05:35:55.59,-69:38:54.7,Dor -NGC2049,G,05:43:15.31,-30:04:42.2,Col -NGC2050,*Ass,05:36:38.82,-69:23:00.7,Dor -NGC2051,OCl,05:36:07.34,-71:00:41.0,Men -NGC2052,Neb,05:37:11.05,-69:46:27.1,Dor -NGC2053,OCl,05:37:39.70,-67:24:46.5,Dor -NGC2054,Other,05:45:15.38,-10:04:59.3,Ori -NGC2055,*Ass,05:36:44.73,-69:29:55.1,Dor -NGC2056,OCl,05:36:33.97,-70:40:18.8,Men -NGC2057,OCl,05:36:55.14,-70:16:08.2,Men -NGC2058,OCl,05:36:54.22,-70:09:44.1,Men -NGC2059,OCl,05:37:00.55,-70:07:44.5,Men -NGC2060,SNR,05:37:46.90,-69:10:18.0,Dor -NGC2061,OCl,05:42:41.77,-34:00:34.3,Col -NGC2062,OCl,05:40:02.70,-66:52:32.7,Dor -NGC2063,Other,05:46:43.03,+08:46:52.0,Ori -NGC2064,RfN,05:46:18.39,+00:00:21.4,Ori -NGC2065,OCl,05:37:38.42,-70:14:11.3,Men -NGC2066,OCl,05:37:43.12,-70:09:59.6,Men -NGC2067,RfN,05:46:31.88,+00:07:52.5,Ori -NGC2068,RfN,05:46:45.82,+00:04:45.5,Ori -NGC2069,Neb,05:38:46.44,-68:58:27.8,Dor -NGC2070,HII,05:38:42.36,-69:06:03.2,Dor -NGC2071,Cl+N,05:47:07.26,+00:17:39.3,Ori -NGC2072,OCl,05:38:24.39,-70:14:02.6,Men -NGC2073,G,05:45:53.91,-21:59:56.9,Lep -NGC2074,Cl+N,05:39:03.58,-69:29:53.2,Dor -NGC2075,Cl+N,05:38:21.35,-70:41:06.1,Men -NGC2076,G,05:46:47.46,-16:46:57.4,Lep -NGC2077,Neb,05:39:36.02,-69:39:25.6,Dor -NGC2078,EmN,05:39:39.45,-69:44:35.4,Dor -NGC2079,HII,05:39:37.60,-69:45:25.9,Dor -NGC2080,HII,05:39:45.82,-69:38:38.8,Dor -NGC2081,Cl+N,05:39:59.39,-69:24:21.2,Dor -NGC2082,G,05:41:51.12,-64:18:04.1,Dor -NGC2083,Neb,05:39:59.22,-69:44:15.3,Dor -NGC2084,Neb,05:40:07.00,-69:45:33.9,Dor -NGC2085,Neb,05:40:09.83,-69:40:22.1,Dor -NGC2086,Neb,05:40:12.88,-69:40:04.3,Dor -NGC2087,G,05:44:16.03,-55:31:56.8,Pic -NGC2088,OCl,05:40:59.81,-68:27:55.3,Dor -NGC2089,G,05:47:51.43,-17:36:08.6,Lep -NGC2090,G,05:47:01.89,-34:15:02.2,Col -NGC2091,OCl,05:40:58.04,-69:26:13.5,Dor -NGC2092,OCl,05:41:22.00,-69:13:27.2,Dor -NGC2093,*Ass,05:41:49.74,-68:55:17.1,Dor -NGC2094,OCl,05:42:08.85,-68:21:49.6,Dor -NGC2095,OCl,05:42:36.16,-67:19:08.0,Dor -NGC2096,OCl,05:42:17.80,-68:27:31.0,Dor -NGC2097,GCl,05:44:16.02,-62:47:08.2,Dor -NGC2098,OCl,05:42:31.03,-68:16:23.4,Dor -NGC2099,OCl,05:52:18.35,+32:33:10.8,Aur -NGC2100,GCl,05:42:09.07,-69:12:42.6,Dor -NGC2101,G,05:46:24.17,-52:05:18.7,Pic -NGC2102,OCl,05:42:20.49,-69:29:13.5,Dor -NGC2103,HII,05:41:40.35,-71:19:59.3,Men -NGC2104,G,05:47:04.73,-51:33:10.5,Pic -NGC2105,GCl,05:44:19.18,-66:55:03.4,Dor -NGC2106,G,05:50:46.63,-21:34:02.1,Lep -NGC2107,OCl,05:43:12.88,-70:38:23.8,Men -NGC2108,GCl,05:43:56.87,-69:10:55.6,Dor -NGC2109,OCl,05:44:22.92,-68:32:52.1,Dor -NGC2110,G,05:52:11.38,-07:27:22.4,Ori -NGC2111,OCl,05:44:32.99,-70:59:35.7,Men -NGC2112,OCl,05:53:45.21,+00:24:38.9,Ori -NGC2113,Cl+N,05:45:24.57,-69:46:27.0,Dor -NGC2114,OCl,05:46:12.11,-68:02:53.9,Dor -NGC2115,GPair,05:51:20.70,-50:35:15.0,Pic -NGC2115A,G,05:51:19.82,-50:34:58.3,Pic -NGC2115B,G,05:51:21.21,-50:35:33.0,Pic -NGC2116,OCl,05:47:15.16,-68:30:28.6,Dor -NGC2117,OCl,05:47:45.92,-67:27:00.6,Dor -NGC2118,OCl,05:47:39.57,-69:07:54.6,Dor -NGC2119,G,05:57:26.95,+11:56:57.1,Ori -NGC2120,GCl,05:50:34.61,-63:40:39.4,Dor -NGC2121,GCl,05:48:12.84,-71:28:47.2,Men -NGC2122,Cl+N,05:48:52.51,-70:04:12.3,Men -NGC2123,GCl,05:51:43.32,-65:19:17.3,Dor -NGC2124,G,05:57:52.23,-20:05:04.7,Lep -NGC2125,OCl,05:50:54.23,-69:28:44.9,Dor -NGC2126,OCl,06:02:32.98,+49:51:57.3,Aur -NGC2127,OCl,05:51:21.28,-69:21:32.6,Dor -NGC2128,G,06:04:34.23,+57:37:39.9,Cam -NGC2129,OCl,06:01:06.54,+23:19:19.8,Gem -NGC2130,GCl,05:52:23.72,-67:20:02.8,Dor -NGC2131,G,05:58:47.52,-26:39:10.8,Lep -NGC2132,OCl,05:55:44.32,-59:55:39.8,Pic -NGC2133,OCl,05:51:28.72,-71:10:30.1,Men -NGC2134,GCl,05:51:57.70,-71:05:51.3,Men -NGC2135,GCl,05:53:35.43,-67:25:46.3,Dor -NGC2136,GCl,05:52:57.75,-69:29:33.9,Dor -NGC2137,OCl,05:53:13.15,-69:28:55.1,Dor -NGC2138,GCl,05:54:50.18,-65:50:13.6,Dor -NGC2139,G,06:01:07.81,-23:40:21.5,Lep -NGC2140,OCl,05:54:16.18,-68:35:59.4,Dor -NGC2141,OCl,06:02:55.06,+10:26:47.3,Ori -NGC2142,*,06:01:50.43,-10:35:52.6,Mon -NGC2143,OCl,06:03:01.56,+05:49:52.6,Ori -NGC2144,G,05:40:57.19,-82:07:09.8,Men -NGC2145,OCl,05:54:23.06,-70:54:03.7,Men -NGC2146,G,06:18:37.71,+78:21:25.3,Cam -NGC2146A,G,06:23:55.20,+78:31:48.4,Cam -NGC2147,OCl,05:55:45.60,-68:12:05.7,Dor -NGC2148,G,05:58:45.85,-59:07:33.4,Pic -NGC2149,RfN,06:03:30.79,-09:43:50.1,Mon -NGC2150,G,05:55:46.34,-69:33:38.9,Dor -NGC2151,OCl,05:56:20.44,-69:01:02.5,Dor -NGC2152,G,06:00:55.29,-50:44:27.3,Pic -NGC2153,GCl,05:57:51.72,-66:24:02.4,Dor -NGC2154,GCl,05:57:37.49,-67:15:49.6,Dor -NGC2155,GCl,05:58:33.21,-65:28:35.3,Dor -NGC2156,GCl,05:57:49.68,-68:27:38.9,Dor -NGC2157,GCl,05:57:34.78,-69:11:50.0,Dor -NGC2158,OCl,06:07:25.61,+24:05:46.2,Gem -NGC2159,GCl,05:58:03.88,-68:37:22.5,Dor -NGC2160,OCl,05:58:12.84,-68:17:22.5,Dor -NGC2161,GCl,05:55:43.06,-74:21:14.2,Men -NGC2162,GCl,06:00:30.17,-63:43:17.2,Dor -NGC2163,RfN,06:07:49.54,+18:39:26.8,Ori -NGC2164,GCl,05:58:56.26,-68:30:57.2,Dor -NGC2165,OCl,06:11:04.21,+51:40:38.3,Aur -NGC2166,OCl,05:59:34.14,-67:56:32.4,Dor -NGC2167,*,06:06:58.56,-06:12:09.5,Mon -NGC2168,OCl,06:09:05.06,+24:20:19.1,Gem -NGC2169,OCl,06:08:24.35,+13:57:53.5,Ori -NGC2170,RfN,06:07:31.82,-06:23:57.5,Mon -NGC2171,Other,05:58:59.64,-70:43:08.8,Men -NGC2172,OCl,06:00:05.84,-68:38:12.9,Dor -NGC2173,GCl,05:57:58.88,-72:58:28.3,Men -NGC2174,Neb,06:09:23.62,+20:39:34.5,Ori -NGC2175,Cl+N,06:09:39.55,+20:29:15.3,Ori -NGC2176,OCl,06:01:19.38,-66:51:11.7,Dor -NGC2177,OCl,06:01:16.49,-67:43:59.7,Dor -NGC2178,G,06:02:47.58,-63:45:49.3,Pic -NGC2179,G,06:08:02.22,-21:44:48.1,Lep -NGC2180,OCl,06:09:36.25,+04:42:41.8,Ori -NGC2181,GCl,06:02:43.66,-65:15:53.5,Dor -NGC2182,RfN,06:09:30.95,-06:19:35.2,Mon -NGC2183,HII,06:10:46.93,-06:12:42.6,Mon -NGC2184,OCl,06:10:59.67,-03:29:42.6,Ori -NGC2185,Neb,06:11:00.47,-06:13:36.7,Mon -NGC2186,OCl,06:12:07.13,+05:27:30.9,Ori -NGC2187,GPair,06:03:48.50,-69:35:00.0,Dor -NGC2187A,G,06:03:44.25,-69:35:17.9,Dor -NGC2187B,G,06:03:52.42,-69:34:38.8,Dor -NGC2188,G,06:10:09.53,-34:06:22.3,Col -NGC2189,Other,06:12:09.99,+01:03:57.5,Ori -NGC2190,GCl,06:01:01.79,-74:43:31.5,Men -NGC2191,G,06:08:23.87,-52:30:44.3,Car -NGC2192,OCl,06:15:17.43,+39:51:18.8,Aur -NGC2193,GCl,06:06:17.52,-65:05:55.6,Dor -NGC2194,OCl,06:13:45.91,+12:48:24.0,Ori -NGC2195,**,06:14:33.81,+17:38:21.8,Ori -NGC2196,G,06:12:09.65,-21:48:21.4,Lep -NGC2197,GCl,06:06:07.31,-67:05:50.0,Dor -NGC2198,Other,06:13:54.91,+00:59:40.8,Ori -NGC2199,G,06:04:44.95,-73:23:59.4,Men -NGC2200,G,06:13:17.58,-43:39:47.4,Pup -NGC2201,G,06:13:31.61,-43:42:17.0,Pup -NGC2202,OCl,06:16:50.75,+05:59:46.2,Ori -NGC2203,GCl,06:04:42.55,-75:26:18.3,Men -NGC2204,OCl,06:15:32.22,-18:39:57.1,CMa -NGC2205,G,06:10:32.73,-62:32:18.3,Pic -NGC2206,G,06:15:59.84,-26:45:55.7,CMa -NGC2207,G,06:16:22.03,-21:22:21.6,CMa -NGC2208,G,06:22:34.68,+51:54:34.1,Aur -NGC2209,GCl,06:08:35.91,-73:50:18.3,Men -NGC2210,GCl,06:11:31.36,-69:07:17.0,Dor -NGC2211,G,06:18:30.36,-18:32:14.2,CMa -NGC2212,G,06:18:35.76,-18:31:10.1,CMa -NGC2213,OCl,06:10:41.94,-71:31:42.4,Men -NGC2214,OCl,06:12:56.93,-68:15:38.6,Dor -NGC2215,OCl,06:20:49.25,-07:17:01.6,Mon -NGC2216,G,06:21:30.74,-22:05:14.8,CMa -NGC2217,G,06:21:39.78,-27:14:01.5,CMa -NGC2218,Other,06:24:41.49,+19:20:28.7,Gem -NGC2219,OCl,06:23:44.32,-04:40:38.2,Mon -NGC2220,Other,06:21:10.76,-44:45:35.2,Pup -NGC2221,G,06:20:15.72,-57:34:42.3,Pic -NGC2222,G,06:20:17.06,-57:32:04.1,Pic -NGC2223,G,06:24:35.91,-22:50:17.7,CMa -NGC2224,Other,06:27:28.59,+12:35:36.2,Gem -NGC2225,OCl,06:26:34.49,-09:37:50.7,Mon -NGC2226,OCl,06:26:37.58,-09:38:34.0,Mon -NGC2227,G,06:25:57.98,-22:00:17.4,CMa -NGC2228,G,06:21:15.54,-64:27:31.8,Dor -NGC2229,G,06:21:23.68,-64:57:24.0,Dor -NGC2230,G,06:21:27.56,-64:59:34.0,Dor -NGC2231,GCl,06:20:42.97,-67:31:06.6,Dor -NGC2232,OCl,06:28:01.13,-04:50:50.8,Mon -NGC2233,G,06:21:40.06,-65:02:00.1,Dor -NGC2234,OCl,06:29:21.68,+16:43:22.3,Gem -NGC2235,G,06:22:21.91,-64:56:03.9,Dor -NGC2236,OCl,06:29:39.70,+06:49:50.5,Mon -NGC2237,Neb,06:30:54.61,+05:02:57.0,Mon -NGC2238,HII,06:30:40.37,+05:00:47.0,Mon -NGC2239,Cl+N,06:31:55.56,+04:56:34.6,Mon -NGC2240,OCl,06:33:10.55,+35:15:00.7,Aur -NGC2241,OCl,06:22:51.36,-68:55:31.4,Dor -NGC2242,PN,06:34:07.24,+44:46:37.3,Aur -NGC2243,OCl,06:29:34.48,-31:16:52.8,CMa -NGC2244,Dup,06:31:55.56,+04:56:34.6,Mon -NGC2245,RfN,06:32:41.25,+10:09:23.9,Mon -NGC2246,Neb,06:32:33.80,+05:07:41.8,Mon -NGC2247,RfN,06:33:05.20,+10:19:20.1,Mon -NGC2248,Other,06:34:35.75,+26:18:16.0,Gem -NGC2249,GCl,06:25:49.57,-68:55:11.2,Dor -NGC2250,OCl,06:33:49.88,-05:05:04.0,Mon -NGC2251,OCl,06:34:38.48,+08:21:59.0,Mon -NGC2252,OCl,06:34:42.97,+05:21:58.5,Mon -NGC2253,G,06:43:41.84,+65:12:22.6,Cam -NGC2254,OCl,06:35:49.66,+07:40:23.8,Mon -NGC2255,G,06:33:58.63,-34:48:45.3,Col -NGC2256,G,06:47:13.97,+74:14:11.5,Cam -NGC2257,GCl,06:30:13.86,-64:19:32.2,Dor -NGC2258,G,06:47:45.80,+74:28:54.0,Cam -NGC2259,OCl,06:38:21.44,+10:53:01.0,Mon -NGC2260,OCl,06:38:03.07,-01:28:22.2,Mon -NGC2261,RfN,06:39:09.51,+08:44:39.6,Mon -NGC2262,OCl,06:39:38.08,+01:08:37.1,Mon -NGC2263,G,06:38:28.84,-24:50:55.3,CMa -NGC2264,Cl+N,06:40:58.25,+09:53:43.7,Mon -NGC2265,OCl,06:41:41.64,+11:54:16.7,Gem -NGC2266,OCl,06:43:19.21,+26:58:10.4,Gem -NGC2267,G,06:40:51.70,-32:28:56.2,CMa -NGC2268,G,07:14:17.44,+84:22:56.2,Cam -NGC2269,OCl,06:43:17.08,+04:37:27.5,Mon -NGC2270,OCl,06:43:57.76,+03:28:42.6,Mon -NGC2271,G,06:42:52.99,-23:28:33.6,CMa -NGC2272,G,06:42:41.30,-27:27:34.2,CMa -NGC2273,G,06:50:08.66,+60:50:44.9,Lyn -NGC2273A,G,06:40:07.09,+60:04:50.4,Lyn -NGC2273B,G,06:46:31.58,+60:20:25.4,Lyn -NGC2274,G,06:47:17.37,+33:34:01.9,Gem -NGC2275,G,06:47:17.93,+33:35:57.2,Gem -NGC2276,G,07:27:14.36,+85:45:16.4,Cep -NGC2277,Other,06:47:47.03,+33:27:04.6,Gem -NGC2278,**,06:48:16.42,+33:23:39.5,Gem -NGC2279,Other,06:48:24.85,+33:24:54.9,Gem -NGC2280,G,06:44:49.11,-27:38:19.0,CMa -NGC2281,OCl,06:48:17.84,+41:04:43.9,Aur -NGC2282,HII,06:46:51.57,+01:18:57.6,Mon -NGC2283,G,06:45:52.69,-18:12:37.2,CMa -NGC2284,Other,06:49:09.56,+33:11:37.7,Gem -NGC2285,**,06:49:36.02,+33:21:52.8,Gem -NGC2286,OCl,06:47:40.17,-03:08:51.6,Mon -NGC2287,OCl,06:45:59.94,-20:45:15.2,CMa -NGC2288,G,06:50:51.96,+33:27:44.8,Gem -NGC2289,G,06:50:53.61,+33:28:43.2,Gem -NGC2290,G,06:50:56.92,+33:26:15.3,Gem -NGC2291,G,06:50:58.64,+33:31:30.3,Gem -NGC2292,G,06:47:39.65,-26:44:46.5,CMa -NGC2293,G,06:47:42.91,-26:45:15.7,CMa -NGC2294,G,06:51:11.30,+33:31:37.6,Gem -NGC2295,G,06:47:23.42,-26:44:10.5,CMa -NGC2296,G,06:48:39.11,-16:54:05.8,CMa -NGC2297,G,06:44:24.59,-63:43:02.4,Pic -NGC2298,GCl,06:48:59.20,-36:00:19.1,Pup -NGC2299,OCl,06:51:53.68,-07:04:57.8,Mon -NGC2300,G,07:32:19.97,+85:42:34.2,Cep -NGC2301,OCl,06:51:45.30,+00:27:33.1,Mon -NGC2302,Dup,06:51:53.68,-07:04:57.8,Mon -NGC2303,G,06:56:17.50,+45:29:33.9,Aur -NGC2304,OCl,06:55:11.61,+17:59:34.0,Gem -NGC2305,G,06:48:37.37,-64:16:23.5,Vol -NGC2306,OCl,06:54:29.56,-07:12:14.9,Mon -NGC2307,G,06:48:50.79,-64:20:07.9,Vol -NGC2308,G,06:58:37.59,+45:12:38.0,Lyn -NGC2309,OCl,06:56:03.61,-07:10:27.5,Mon -NGC2310,G,06:53:53.96,-40:51:45.4,Pup -NGC2311,OCl,06:57:47.55,-04:36:40.8,Mon -NGC2312,OCl,06:58:46.74,+10:17:40.1,Mon -NGC2313,*,06:58:02.80,-07:56:42.1,Mon -NGC2314,G,07:10:32.55,+75:19:36.0,Cam -NGC2315,G,07:02:33.06,+50:35:26.1,Lyn -NGC2316,Neb,06:59:40.85,-07:46:39.9,Mon -NGC2317,Neb,06:59:41.59,-07:46:28.2,Mon -NGC2318,OCl,06:59:27.02,-13:41:54.2,CMa -NGC2319,OCl,07:00:32.21,+03:02:31.9,Mon -NGC2320,G,07:05:42.03,+50:34:51.8,Lyn -NGC2321,G,07:05:59.00,+50:45:21.7,Lyn -NGC2322,G,07:06:00.30,+50:30:37.1,Lyn -NGC2323,OCl,07:02:40.47,-08:21:50.5,Mon -NGC2324,OCl,07:04:07.96,+01:02:40.6,Mon -NGC2325,G,07:02:40.40,-28:41:50.0,CMa -NGC2326,G,07:08:11.02,+50:40:55.0,Lyn -NGC2326A,G,07:08:34.21,+50:37:53.0,Lyn -NGC2327,RfN,07:04:07.22,-11:18:50.8,CMa -NGC2328,G,07:02:36.20,-42:04:06.8,Pup -NGC2329,G,07:09:08.01,+48:36:55.5,Lyn -NGC2330,G,07:09:28.40,+50:09:09.1,Lyn -NGC2331,OCl,07:06:59.83,+27:15:41.7,Gem -NGC2332,G,07:09:34.16,+50:10:56.2,Lyn -NGC2333,G,07:08:21.34,+35:10:12.1,Gem -NGC2334,G,07:11:33.65,+50:14:53.7,Lyn -NGC2335,OCl,07:06:49.45,-10:01:43.1,Mon -NGC2336,G,07:27:04.05,+80:10:41.1,Cam -NGC2337,G,07:10:13.58,+44:27:26.3,Lyn -NGC2338,OCl,07:07:47.37,-05:43:11.0,Mon -NGC2339,G,07:08:20.54,+18:46:48.9,Gem -NGC2340,G,07:11:10.83,+50:10:29.1,Lyn -NGC2341,G,07:09:12.04,+20:36:10.5,Gem -NGC2342,G,07:09:18.08,+20:38:09.5,Gem -NGC2343,OCl,07:08:06.80,-10:37:00.5,Mon -NGC2344,G,07:12:28.66,+47:10:00.1,Lyn -NGC2345,OCl,07:08:18.79,-13:11:37.5,CMa -NGC2346,PN,07:09:23.01,-00:48:32.9,Mon -NGC2347,G,07:16:03.69,+64:42:32.1,Cam -NGC2348,OCl,07:03:02.52,-67:23:38.2,Vol -NGC2349,OCl,07:10:48.15,-08:35:35.7,Mon -NGC2350,G,07:13:12.19,+12:15:57.9,CMi -NGC2351,OCl,07:13:32.03,-10:29:29.1,Mon -NGC2352,OCl,07:13:05.44,-24:02:45.9,CMa -NGC2353,OCl,07:14:30.31,-10:15:57.1,Mon -NGC2354,OCl,07:14:05.27,-25:41:20.1,CMa -NGC2355,OCl,07:16:59.26,+13:44:59.5,Gem -NGC2356,Dup,07:16:59.26,+13:44:59.5,Gem -NGC2357,G,07:17:40.99,+23:21:24.3,Gem -NGC2358,OCl,07:16:56.33,-17:07:01.5,CMa -NGC2359,HII,07:18:30.98,-13:13:37.9,CMa -NGC2360,OCl,07:17:43.12,-15:38:28.7,CMa -NGC2361,Neb,07:18:23.80,-13:12:34.4,CMa -NGC2362,OCl,07:18:41.47,-24:57:15.1,CMa -NGC2363,G,07:28:29.59,+69:11:34.3,Cam -NGC2364,OCl,07:20:46.47,-07:32:59.0,Mon -NGC2365,G,07:22:22.55,+22:04:59.8,Gem -NGC2366,G,07:28:54.66,+69:12:56.8,Cam -NGC2367,OCl,07:20:04.54,-21:53:02.7,CMa -NGC2368,OCl,07:21:06.30,-10:22:18.4,Mon -NGC2369,G,07:16:37.73,-62:20:37.4,Car -NGC2369A,G,07:18:43.54,-62:56:10.6,Car -NGC2369B,G,07:20:29.63,-62:03:14.3,Car -NGC2370,G,07:25:01.66,+23:46:59.6,Gem -NGC2371,PN,07:25:34.66,+29:29:26.3,Gem -NGC2372,Dup,07:25:34.66,+29:29:26.3,Gem -NGC2373,G,07:26:36.95,+33:49:25.3,Gem -NGC2374,OCl,07:23:56.07,-13:15:48.1,CMa -NGC2375,G,07:27:09.48,+33:49:54.5,Gem -NGC2376,G,07:26:35.91,+23:04:22.6,Gem -NGC2377,G,07:24:56.82,-09:39:33.5,Mon -NGC2378,**,07:27:24.20,+33:49:53.7,Gem -NGC2379,G,07:27:26.26,+33:48:40.5,Gem -NGC2380,G,07:23:54.75,-27:31:44.6,CMa -NGC2381,G,07:19:57.31,-63:04:01.3,Car -NGC2382,Dup,07:23:54.75,-27:31:44.6,CMa -NGC2383,OCl,07:24:39.90,-20:56:51.5,CMa -NGC2384,OCl,07:25:09.83,-21:01:11.5,CMa -NGC2385,G,07:28:28.07,+33:50:16.1,Gem -NGC2386,Other,07:28:37.92,+33:46:25.7,Gem -NGC2387,G,07:28:57.93,+36:52:47.3,Aur -NGC2388,G,07:28:53.44,+33:49:08.7,Gem -NGC2389,G,07:29:04.65,+33:51:39.5,Gem -NGC2390,*,07:29:04.27,+33:50:12.5,Gem -NGC2391,*,07:29:07.49,+33:49:33.4,Gem -NGC2392,PN,07:29:10.76,+20:54:42.6,Gem -NGC2393,G,07:30:04.63,+34:01:40.0,Gem -NGC2394,OCl,07:28:36.51,+07:05:11.6,CMi -NGC2395,OCl,07:27:12.85,+13:36:29.5,Gem -NGC2396,OCl,07:28:02.92,-11:43:10.8,Pup -NGC2397,G,07:21:19.98,-69:00:05.3,Vol -NGC2397A,G,07:21:07.91,-69:06:55.2,Vol -NGC2397B,G,07:21:55.57,-68:50:44.9,Vol -NGC2398,G,07:30:16.16,+24:29:16.4,Gem -NGC2399,Other,07:29:50.17,-00:12:51.7,CMi -NGC2400,Other,07:29:54.86,-00:12:53.0,CMi -NGC2401,OCl,07:29:24.40,-13:57:58.4,Pup -NGC2402,GPair,07:30:47.20,+09:39:02.0,CMi -NGC2402 NED01,G,07:30:46.58,+09:38:48.8,CMi -NGC2402 NED02,G,07:30:47.80,+09:39:13.0,CMi -NGC2403,G,07:36:51.40,+65:36:09.2,Cam -NGC2404,HII,07:37:06.73,+65:36:38.9,Cam -NGC2405,G,07:32:13.98,+25:54:23.0,Gem -NGC2406,G,07:31:47.74,+18:17:16.5,Gem -NGC2407,G,07:31:56.67,+18:19:59.1,Gem -NGC2408,OCl,07:40:31.92,+71:40:05.5,Cam -NGC2409,OCl,07:31:36.72,-17:11:25.5,Pup -NGC2410,G,07:35:02.26,+32:49:19.6,Gem -NGC2411,G,07:34:36.33,+18:16:53.6,Gem -NGC2412,*,07:34:21.48,+08:32:52.0,CMi -NGC2413,OCl,07:33:16.50,-13:05:44.0,Pup -NGC2414,OCl,07:33:12.80,-15:27:13.9,Pup -NGC2415,G,07:36:56.69,+35:14:31.1,Lyn -NGC2416,G,07:35:41.52,+11:36:43.3,CMi -NGC2417,G,07:30:12.09,-62:15:09.5,Car -NGC2418,G,07:36:37.51,+17:53:02.1,Gem -NGC2419,GCl,07:38:07.95,+38:52:47.9,Lyn -NGC2420,OCl,07:38:23.90,+21:34:26.7,Gem -NGC2421,OCl,07:36:11.81,-20:36:44.1,Pup -NGC2422,OCl,07:36:35.02,-14:28:57.4,Pup -NGC2423,OCl,07:37:06.73,-13:52:17.4,Pup -NGC2424,G,07:40:39.29,+39:13:59.9,Lyn -NGC2425,OCl,07:38:17.63,-14:52:40.2,Pup -NGC2426,G,07:43:18.46,+52:19:06.6,Lyn -NGC2427,G,07:36:28.18,-47:38:08.0,Pup -NGC2428,OCl,07:39:21.77,-16:31:44.5,Pup -NGC2429,GPair,07:43:49.70,+52:21:11.0,Lyn -NGC2429A,G,07:43:47.58,+52:21:26.7,Lyn -NGC2429B,G,07:43:51.83,+52:20:54.5,Lyn -NGC2430,OCl,07:39:41.05,-16:17:45.8,Pup -NGC2431,G,07:45:13.39,+53:04:30.4,Lyn -NGC2432,OCl,07:40:53.87,-19:04:08.7,Pup -NGC2433,Other,07:42:43.63,+09:15:33.3,CMi -NGC2434,G,07:34:51.16,-69:17:02.9,Vol -NGC2435,G,07:44:13.58,+31:39:04.0,Gem -NGC2436,Dup,07:45:13.39,+53:04:30.4,Lyn -NGC2437,OCl,07:41:46.82,-14:48:36.0,Pup -NGC2438,PN,07:41:50.39,-14:44:08.8,Pup -NGC2439,OCl,07:40:45.41,-31:41:32.7,Pup -NGC2440,PN,07:41:55.36,-18:12:30.5,Pup -NGC2441,G,07:51:54.74,+73:00:56.5,Cam -NGC2442,G,07:36:23.84,-69:31:51.0,Vol -NGC2443,Dup,07:36:23.84,-69:31:51.0,Vol -NGC2444,G,07:46:53.04,+39:01:54.7,Lyn -NGC2445,G,07:46:55.10,+39:00:54.6,Lyn -NGC2446,G,07:48:39.24,+54:36:42.9,Lyn -NGC2447,OCl,07:44:29.23,-23:51:11.1,Pup -NGC2448,OCl,07:44:33.19,-24:40:23.4,Pup -NGC2449,G,07:47:20.28,+26:55:48.6,Gem -NGC2450,G,07:47:32.28,+27:01:08.9,Gem -NGC2451,OCl,07:45:15.01,-37:58:02.8,Pup -NGC2452,PN,07:47:26.37,-27:20:02.4,Pup -NGC2453,OCl,07:47:34.13,-27:11:41.3,Pup -NGC2454,G,07:50:35.02,+16:22:06.9,Gem -NGC2455,OCl,07:48:58.60,-21:17:52.6,Pup -NGC2456,G,07:54:10.66,+55:29:43.1,Lyn -NGC2457,G,07:54:45.73,+55:32:47.6,Lyn -NGC2458,G,07:55:51.37,+56:42:38.2,Lyn -NGC2459,OCl,07:52:01.77,+09:33:26.7,CMi -NGC2460,G,07:56:52.29,+60:20:57.8,Cam -NGC2461,*,07:56:26.26,+56:40:23.9,Lyn -NGC2462,G,07:56:32.06,+56:41:13.6,Lyn -NGC2463,G,07:57:12.48,+56:40:35.5,Lyn -NGC2464,Other,07:57:32.69,+56:41:26.0,Lyn -NGC2465,*,07:57:26.10,+56:49:20.9,Lyn -NGC2466,G,07:45:16.05,-71:24:37.5,Vol -NGC2467,Cl+N,07:52:23.43,-26:26:36.0,Pup -NGC2468,G,07:58:02.46,+56:21:34.5,Lyn -NGC2469,G,07:58:03.42,+56:40:49.7,Lyn -NGC2470,G,07:54:20.77,+04:27:34.9,CMi -NGC2471,**,07:58:32.96,+56:46:34.2,Lyn -NGC2472,G,07:58:41.90,+56:42:04.7,Lyn -NGC2473,G,07:55:34.83,+56:44:08.9,Lyn -NGC2474,G,07:57:58.95,+52:51:26.2,Lyn -NGC2475,G,07:58:00.44,+52:51:42.1,Lyn -NGC2476,G,07:56:45.18,+39:55:40.4,Lyn -NGC2477,OCl,07:52:09.78,-38:31:59.7,Pup -NGC2478,Dup,07:36:35.02,-14:28:57.4,Pup -NGC2479,OCl,07:55:06.07,-17:42:28.1,Pup -NGC2480,G,07:57:10.44,+23:46:47.3,Gem -NGC2481,G,07:57:13.75,+23:46:04.0,Gem -NGC2482,OCl,07:55:10.37,-24:15:16.7,Pup -NGC2483,OCl,07:55:38.79,-27:53:12.6,Pup -NGC2484,G,07:58:28.11,+37:47:11.8,Lyn -NGC2485,G,07:56:48.66,+07:28:40.6,CMi -NGC2486,G,07:57:56.49,+25:09:39.1,Gem -NGC2487,G,07:58:20.46,+25:08:57.2,Gem -NGC2488,G,08:01:45.98,+56:33:13.9,Lyn -NGC2489,OCl,07:56:14.94,-30:03:39.0,Pup -NGC2490,G,07:59:17.87,+27:04:40.3,Gem -NGC2491,G,07:58:27.38,+07:59:01.7,CMi -NGC2492,G,07:59:29.71,+27:01:35.2,Gem -NGC2493,G,08:00:23.63,+39:49:49.5,Lyn -NGC2494,G,07:59:07.14,-00:38:16.5,Mon -NGC2495,G,08:00:33.15,+39:50:24.1,Lyn -NGC2496,G,07:58:37.35,+08:01:47.3,CMi -NGC2497,G,08:02:11.13,+56:56:32.4,Lyn -NGC2498,G,07:59:38.85,+24:58:56.4,Gem -NGC2499,G,07:58:51.71,+07:29:35.9,CMi -NGC2500,G,08:01:53.21,+50:44:13.6,Lyn -NGC2501,G,07:58:30.04,-14:21:15.4,Pup -NGC2502,G,07:55:51.52,-52:18:24.4,Car -NGC2503,G,08:00:36.68,+22:24:00.2,Cnc -NGC2504,G,07:59:52.30,+05:36:29.2,CMi -NGC2505,G,08:04:06.84,+53:32:57.2,Lyn -NGC2506,OCl,08:00:01.78,-10:46:10.7,Mon -NGC2507,G,08:01:37.21,+15:42:35.1,Cnc -NGC2508,G,08:01:57.22,+08:33:06.8,CMi -NGC2509,OCl,08:00:47.82,-19:03:01.9,Pup -NGC2510,G,08:02:10.68,+09:29:09.5,CMi -NGC2511,G,08:02:15.01,+09:23:39.9,CMi -NGC2512,G,08:03:07.85,+23:23:30.6,Cnc -NGC2513,G,08:02:24.67,+09:24:48.8,Cnc -NGC2514,G,08:02:49.64,+15:48:29.8,Cnc -NGC2515,**,08:03:21.29,+20:11:16.8,Cnc -NGC2516,OCl,07:58:07.06,-60:45:12.5,Car -NGC2517,G,08:02:47.07,-12:19:04.2,Pup -NGC2518,G,08:07:20.25,+51:07:53.8,Lyn -NGC2519,*,08:07:58.83,+51:07:41.6,Lyn -NGC2520,OCl,08:04:58.18,-28:08:48.0,Pup -NGC2521,G,08:08:49.37,+57:46:10.6,Lyn -NGC2522,G,08:06:13.44,+17:42:23.7,Cnc -NGC2523,G,08:15:00.09,+73:34:44.3,Cam -NGC2523A,G,08:04:08.46,+74:02:52.0,Cam -NGC2523B,G,08:12:57.09,+73:33:47.7,Cam -NGC2523C,G,08:17:44.29,+73:19:03.4,Cam -NGC2524,G,08:08:09.59,+39:09:26.8,Lyn -NGC2525,G,08:05:38.04,-11:25:37.3,Pup -NGC2526,G,08:06:58.63,+08:00:14.2,Cnc -NGC2527,Dup,08:04:58.18,-28:08:48.0,Pup -NGC2528,G,08:07:24.83,+39:11:40.1,Lyn -NGC2529,Other,08:07:49.11,+17:49:15.0,Cnc -NGC2530,G,08:07:55.61,+17:49:06.6,Cnc -NGC2531,Other,08:08:01.10,+17:49:14.2,Cnc -NGC2532,G,08:10:15.18,+33:57:23.9,Lyn -NGC2533,OCl,08:07:04.10,-29:53:01.9,Pup -NGC2534,G,08:12:54.15,+55:40:19.4,Lyn -NGC2535,G,08:11:13.49,+25:12:24.5,Cnc -NGC2536,G,08:11:15.92,+25:10:45.7,Cnc -NGC2537,G,08:13:14.64,+45:59:23.3,Lyn -NGC2537A,G,08:13:41.03,+45:59:37.5,Lyn -NGC2538,G,08:11:23.07,+03:37:59.5,CMi -NGC2539,OCl,08:10:36.98,-12:49:14.4,Pup -NGC2540,G,08:12:46.44,+26:21:42.4,Cnc -NGC2541,G,08:14:40.12,+49:03:42.2,Lyn -NGC2542,**,08:11:16.39,-12:55:37.5,Pup -NGC2543,G,08:12:57.92,+36:15:16.7,Lyn -NGC2544,G,08:21:40.34,+73:59:18.1,Cam -NGC2545,G,08:14:14.16,+21:21:19.7,Cnc -NGC2546,OCl,08:12:15.63,-37:35:39.5,Pup -NGC2547,OCl,08:10:09.49,-49:12:20.4,Vel -NGC2548,OCl,08:13:43.18,-05:45:01.6,Hya -NGC2549,G,08:18:58.35,+57:48:11.0,Lyn -NGC2550,G,08:24:33.86,+74:00:44.2,Cam -NGC2550A,G,08:28:39.94,+73:44:52.8,Cam -NGC2551,G,08:24:50.28,+73:24:43.3,Cam -NGC2552,G,08:19:20.53,+50:00:34.7,Lyn -NGC2553,G,08:17:35.00,+20:54:11.1,Cnc -NGC2554,G,08:17:53.49,+23:28:19.7,Cnc -NGC2555,G,08:17:56.41,+00:44:44.5,Hya -NGC2556,G,08:19:00.85,+20:56:13.1,Cnc -NGC2557,G,08:19:10.75,+21:26:08.8,Cnc -NGC2558,G,08:19:12.76,+20:30:38.7,Cnc -NGC2559,G,08:17:06.07,-27:27:21.0,Pup -NGC2560,G,08:19:51.89,+20:59:05.9,Cnc -NGC2561,G,08:19:36.97,+04:39:26.0,Hya -NGC2562,G,08:20:23.66,+21:07:53.3,Cnc -NGC2563,G,08:20:35.69,+21:04:04.1,Cnc -NGC2564,G,08:18:30.01,-21:48:58.0,Pup -NGC2565,G,08:19:48.32,+22:01:53.2,Cnc -NGC2566,G,08:18:45.66,-25:29:58.3,Pup -NGC2567,OCl,08:18:35.18,-30:38:08.2,Pup -NGC2568,OCl,08:18:18.12,-37:06:19.4,Pup -NGC2569,G,08:21:21.14,+20:52:03.0,Cnc -NGC2570,G,08:21:22.54,+20:54:38.0,Cnc -NGC2571,OCl,08:18:56.35,-29:44:57.4,Pup -NGC2572,G,08:21:24.63,+19:08:52.0,Cnc -NGC2573,G,01:41:37.33,-89:20:04.3,Oct -NGC2573B,G,23:07:32.82,-89:06:59.4,Oct -NGC2574,G,08:20:48.16,-08:55:06.5,Hya -NGC2575,G,08:22:44.96,+24:17:48.9,Cnc -NGC2576,G,08:22:57.70,+25:44:19.4,Cnc -NGC2577,G,08:22:43.45,+22:33:11.1,Cnc -NGC2578,G,08:21:24.27,-13:19:04.3,Pup -NGC2579,Cl+N,08:20:53.06,-36:13:01.7,Pup -NGC2580,OCl,08:21:27.90,-30:17:36.5,Pup -NGC2581,G,08:24:30.94,+18:35:49.5,Cnc -NGC2582,G,08:25:12.07,+20:20:05.1,Cnc -NGC2583,G,08:23:07.93,-05:00:08.6,Hya -NGC2584,G,08:23:15.45,-04:58:13.8,Hya -NGC2585,G,08:23:26.28,-04:54:54.8,Hya -NGC2586,Other,08:23:31.41,-04:57:07.0,Hya -NGC2587,OCl,08:23:24.08,-29:30:31.4,Pup -NGC2588,OCl,08:23:09.57,-32:58:30.6,Pup -NGC2589,Other,08:24:29.47,-08:46:04.5,Hya -NGC2590,G,08:25:02.00,-00:35:29.0,Hya -NGC2591,G,08:37:25.53,+78:01:35.1,Cam -NGC2592,G,08:27:08.05,+25:58:13.1,Cnc -NGC2593,G,08:26:47.83,+17:22:29.7,Cnc -NGC2594,G,08:27:17.16,+25:52:43.7,Cnc -NGC2595,G,08:27:42.02,+21:28:44.8,Cnc -NGC2596,G,08:27:26.52,+17:17:02.7,Cnc -NGC2597,**,08:29:57.41,+21:30:06.8,Cnc -NGC2598,G,08:30:02.52,+21:29:19.2,Cnc -NGC2599,G,08:32:11.30,+22:33:38.0,Cnc -NGC2600,G,08:34:45.05,+52:42:56.5,UMa -NGC2601,G,08:25:30.66,-68:07:03.6,Vol -NGC2602,G,08:35:04.25,+52:49:53.5,UMa -NGC2603,G,08:34:31.19,+52:50:24.8,UMa -NGC2604,G,08:33:23.14,+29:32:19.7,Cnc -NGC2604B,G,08:33:35.66,+29:29:58.9,Cnc -NGC2605,G,08:34:53.31,+52:48:15.5,UMa -NGC2606,G,08:35:34.44,+52:47:20.1,UMa -NGC2607,G,08:33:56.66,+26:58:21.6,Cnc -NGC2608,G,08:35:17.33,+28:28:24.3,Cnc -NGC2609,OCl,08:29:29.65,-61:06:36.8,Car -NGC2610,PN,08:33:23.40,-16:08:58.0,Hya -NGC2611,G,08:35:29.17,+25:01:39.0,Cnc -NGC2612,G,08:33:50.10,-13:10:28.3,Hya -NGC2613,G,08:33:22.84,-22:58:25.2,Pyx -NGC2614,G,08:42:47.95,+72:58:35.3,UMa -NGC2615,G,08:34:33.35,-02:32:48.5,Hya -NGC2616,G,08:35:34.07,-01:51:00.5,Hya -NGC2617,G,08:35:38.79,-04:05:17.6,Hya -NGC2618,G,08:35:53.54,+00:42:25.6,Hya -NGC2619,G,08:37:32.71,+28:42:18.7,Cnc -NGC2620,G,08:37:28.24,+24:56:49.0,Cnc -NGC2621,G,08:37:36.98,+24:59:59.3,Cnc -NGC2622,G,08:38:10.94,+24:53:43.0,Cnc -NGC2623,G,08:38:24.08,+25:45:16.7,Cnc -NGC2624,G,08:38:09.63,+19:43:32.4,Cnc -NGC2625,G,08:38:23.22,+19:43:00.0,Cnc -NGC2626,RfN,08:35:28.88,-40:40:06.0,Vel -NGC2627,OCl,08:37:14.94,-29:57:01.5,Pyx -NGC2628,G,08:40:22.71,+23:32:22.8,Cnc -NGC2629,G,08:47:15.82,+72:59:08.3,UMa -NGC2630,Other,08:47:07.30,+73:00:01.1,UMa -NGC2631,Other,08:47:07.30,+73:00:01.1,UMa -NGC2632,OCl,08:40:22.20,+19:40:19.4,Cnc -NGC2633,G,08:48:04.58,+74:05:55.9,Cam -NGC2634,G,08:48:25.39,+73:58:01.8,Cam -NGC2634A,G,08:48:38.14,+73:56:21.4,Cam -NGC2635,OCl,08:38:25.99,-34:46:17.7,Pyx -NGC2636,G,08:48:24.43,+73:40:16.1,Cam -NGC2637,G,08:41:13.49,+19:41:29.2,Cnc -NGC2638,G,08:42:25.77,+37:13:15.7,Lyn -NGC2639,G,08:43:38.08,+50:12:20.0,UMa -NGC2640,G,08:37:24.62,-55:07:25.5,Car -NGC2641,G,08:47:57.49,+72:53:44.8,UMa -NGC2642,G,08:40:44.37,-04:07:18.2,Hya -NGC2643,G,08:41:51.74,+19:42:09.1,Cnc -NGC2644,G,08:41:31.86,+04:58:49.2,Hya -NGC2645,OCl,08:39:03.12,-46:13:38.3,Vel -NGC2646,G,08:50:22.05,+73:27:47.1,Cam -NGC2647,G,08:42:43.10,+19:39:02.2,Cnc -NGC2648,G,08:42:39.80,+14:17:08.2,Cnc -NGC2649,G,08:44:08.27,+34:43:02.1,Lyn -NGC2650,G,08:49:58.35,+70:17:58.0,UMa -NGC2651,G,08:43:55.15,+11:46:15.6,Cnc -NGC2652,Dup,09:42:33.28,-03:41:56.9,Sex -NGC2653,**,08:54:55.56,+78:23:36.8,Cam -NGC2654,G,08:49:11.87,+60:13:16.0,UMa -NGC2655,G,08:55:37.73,+78:13:23.1,Cam -NGC2656,G,08:47:53.07,+53:52:34.2,UMa -NGC2657,G,08:45:15.81,+09:38:43.8,Cnc -NGC2658,OCl,08:43:27.34,-32:39:22.4,Pyx -NGC2659,OCl,08:42:33.02,-45:00:01.9,Vel -NGC2660,OCl,08:42:37.99,-47:12:02.4,Vel -NGC2661,G,08:45:59.54,+12:37:11.7,Cnc -NGC2662,G,08:45:32.03,-15:07:16.5,Hya -NGC2663,G,08:45:08.25,-33:47:41.1,Pyx -NGC2664,OCl,08:47:07.03,+12:36:20.8,Cnc -NGC2665,G,08:46:00.99,-19:18:10.4,Hya -NGC2666,OCl,08:49:47.02,+44:42:13.2,Lyn -NGC2667,G,08:48:27.25,+19:01:10.2,Cnc -NGC2667B,G,08:48:30.17,+19:02:38.0,Cnc -NGC2668,G,08:49:22.56,+36:42:37.2,Lyn -NGC2669,OCl,08:46:22.57,-52:56:51.1,Vel -NGC2670,OCl,08:45:29.48,-48:47:29.9,Vel -NGC2671,OCl,08:46:11.89,-41:52:37.8,Vel -NGC2672,G,08:49:21.89,+19:04:29.9,Cnc -NGC2673,G,08:49:24.14,+19:04:27.1,Cnc -NGC2674,G,08:49:13.21,-14:17:39.2,Hya -NGC2675,G,08:52:04.95,+53:37:02.3,UMa -NGC2676,G,08:51:35.65,+47:33:27.6,UMa -NGC2677,G,08:50:01.33,+19:00:35.2,Cnc -NGC2678,OCl,08:50:02.75,+11:20:17.2,Cnc -NGC2679,G,08:51:32.94,+30:51:55.3,Cnc -NGC2680,**,08:51:33.49,+30:51:56.9,Cnc -NGC2681,G,08:53:32.74,+51:18:49.2,UMa -NGC2682,OCl,08:51:20.13,+11:48:43.0,Cnc -NGC2683,G,08:52:41.33,+33:25:18.3,Lyn -NGC2684,G,08:54:54.04,+49:09:37.4,UMa -NGC2685,G,08:55:34.71,+58:44:03.8,UMa -NGC2686,GPair,08:54:59.80,+49:08:32.5,UMa -NGC2686A,G,08:54:58.91,+49:08:32.4,UMa -NGC2686B,G,08:55:00.59,+49:08:32.8,UMa -NGC2687,GPair,08:55:05.50,+49:09:21.3,UMa -NGC2687A,G,08:55:05.02,+49:09:23.4,UMa -NGC2687B,G,08:55:06.04,+49:09:22.0,UMa -NGC2688,G,08:55:11.59,+49:07:21.4,UMa -NGC2689,G,08:55:25.41,+49:06:55.7,UMa -NGC2690,G,08:52:38.03,-02:36:11.6,Hya -NGC2691,G,08:54:46.34,+39:32:19.2,Lyn -NGC2692,G,08:56:58.01,+52:03:57.4,UMa -NGC2693,G,08:56:59.27,+51:20:50.8,UMa -NGC2694,G,08:56:59.26,+51:19:55.1,UMa -NGC2695,G,08:54:27.07,-03:04:01.3,Hya -NGC2696,G,08:50:42.05,-05:00:35.2,Hya -NGC2697,G,08:54:59.40,-02:59:15.4,Hya -NGC2698,G,08:55:36.51,-03:11:02.2,Hya -NGC2699,G,08:55:48.80,-03:07:39.3,Hya -NGC2700,*,08:55:50.58,-03:06:59.0,Hya -NGC2701,G,08:59:05.74,+53:46:18.0,UMa -NGC2702,*,08:55:54.64,-03:03:55.2,Hya -NGC2703,**,08:55:47.13,-03:18:24.9,Hya -NGC2704,G,08:56:47.69,+39:22:55.9,Lyn -NGC2705,*,08:56:00.05,-03:00:53.6,Hya -NGC2706,G,08:56:12.31,-02:33:48.4,Hya -NGC2707,*,08:56:05.68,-03:03:59.1,Hya -NGC2708,G,08:56:08.05,-03:21:36.4,Hya -NGC2709,G,08:56:12.86,-03:14:35.6,Hya -NGC2710,G,08:59:48.35,+55:42:23.0,UMa -NGC2711,G,08:57:23.60,+17:17:16.9,Cnc -NGC2712,G,08:59:30.47,+44:54:50.0,Lyn -NGC2713,G,08:57:20.51,+02:55:16.7,Hya -NGC2714,G,08:53:29.82,-59:13:01.6,Car -NGC2715,G,09:08:06.20,+78:05:06.6,Cam -NGC2716,G,08:57:35.88,+03:05:24.8,Hya -NGC2717,G,08:57:01.12,-24:40:25.5,Pyx -NGC2718,G,08:58:50.47,+06:17:34.8,Hya -NGC2719,G,09:00:15.46,+35:43:39.5,Lyn -NGC2719A,G,09:00:15.94,+35:43:12.5,Lyn -NGC2720,G,08:59:08.06,+11:08:57.2,Cnc -NGC2721,G,08:58:56.52,-04:54:07.0,Hya -NGC2722,G,08:58:46.17,-03:42:36.4,Hya -NGC2723,G,09:00:14.36,+03:10:39.9,Hya -NGC2724,G,09:01:01.82,+35:45:43.4,Lyn -NGC2725,G,09:01:03.22,+11:05:54.2,Cnc -NGC2726,G,09:04:56.77,+59:55:58.5,UMa -NGC2727,Dup,08:56:08.05,-03:21:36.4,Hya -NGC2728,G,09:01:40.93,+11:04:58.7,Cnc -NGC2729,G,09:01:28.62,+03:43:14.2,Hya -NGC2730,G,09:02:15.83,+16:50:17.9,Cnc -NGC2731,G,09:02:08.41,+08:18:06.0,Cnc -NGC2732,G,09:13:24.73,+79:11:14.4,Cam -NGC2733,Dup,08:58:46.17,-03:42:36.4,Hya -NGC2734,G,09:03:01.61,+16:51:49.0,Cnc -NGC2735,G,09:02:38.64,+25:56:04.3,Cnc -NGC2735A,G,09:02:41.87,+25:56:18.2,Cnc -NGC2736,HII,09:00:16.94,-45:56:53.0,Vel -NGC2737,G,09:03:59.72,+21:54:23.6,Cnc -NGC2738,G,09:04:00.45,+21:58:03.4,Cnc -NGC2739,G,09:06:02.83,+51:44:40.9,UMa -NGC2740,G,09:06:04.99,+51:44:07.1,UMa -NGC2741,G,09:03:16.50,+18:15:39.9,Cnc -NGC2742,G,09:07:33.53,+60:28:45.6,UMa -NGC2742A,G,09:09:58.07,+62:14:50.5,UMa -NGC2743,G,09:04:54.06,+25:00:14.1,Cnc -NGC2744,GPair,09:04:38.90,+18:27:37.0,Cnc -NGC2744 NED01,G,09:04:38.70,+18:27:22.0,Cnc -NGC2744 NED02,G,09:04:39.00,+18:27:52.0,Cnc -NGC2745,G,09:04:39.33,+18:15:26.5,Cnc -NGC2746,G,09:05:59.44,+35:22:38.6,Lyn -NGC2747,G,09:05:18.34,+18:26:31.9,Cnc -NGC2748,G,09:13:43.02,+76:28:31.2,Cam -NGC2749,G,09:05:21.32,+18:18:47.2,Cnc -NGC2750,G,09:05:47.91,+25:26:14.7,Cnc -NGC2751,G,09:05:32.40,+18:15:44.3,Cnc -NGC2752,G,09:05:43.03,+18:20:23.0,Cnc -NGC2753,G,09:07:08.26,+25:20:32.9,Cnc -NGC2754,G,09:05:11.21,-19:05:05.5,Hya -NGC2755,G,09:07:58.31,+41:42:32.2,Lyn -NGC2756,G,09:09:00.93,+53:50:58.3,UMa -NGC2757,Other,09:05:25.76,-19:02:52.1,Hya -NGC2758,G,09:05:31.20,-19:02:34.0,Hya -NGC2759,G,09:08:37.29,+37:37:17.8,Lyn -NGC2760,G,09:24:13.04,+76:31:52.6,Dra -NGC2761,G,09:07:30.83,+18:26:05.1,Cnc -NGC2762,G,09:09:54.53,+50:25:05.7,UMa -NGC2763,G,09:06:49.05,-15:29:59.2,Hya -NGC2764,G,09:08:17.47,+21:26:36.0,Cnc -NGC2765,G,09:07:36.64,+03:23:34.5,Hya -NGC2766,G,09:08:47.54,+29:51:53.2,Cnc -NGC2767,G,09:10:11.88,+50:24:04.7,UMa -NGC2768,G,09:11:37.50,+60:02:14.0,UMa -NGC2769,G,09:10:32.16,+50:25:59.8,UMa -NGC2770,G,09:09:33.71,+33:07:24.7,Lyn -NGC2771,G,09:10:39.66,+50:22:47.5,UMa -NGC2772,G,09:07:41.87,-23:37:17.1,Pyx -NGC2773,G,09:09:44.19,+07:10:25.3,Cnc -NGC2774,G,09:10:39.92,+18:41:47.7,Cnc -NGC2775,G,09:10:20.12,+07:02:16.6,Cnc -NGC2776,G,09:12:14.51,+44:57:17.4,Lyn -NGC2777,G,09:10:41.83,+07:12:24.1,Cnc -NGC2778,G,09:12:24.37,+35:01:39.1,Lyn -NGC2779,G,09:12:28.29,+35:03:13.5,Lyn -NGC2780,G,09:12:44.37,+34:55:32.1,Lyn -NGC2781,G,09:11:27.52,-14:49:00.6,Hya -NGC2782,G,09:14:05.11,+40:06:49.3,Lyn -NGC2783,G,09:13:39.46,+29:59:34.7,Cnc -NGC2783B,G,09:13:33.15,+30:00:00.5,Cnc -NGC2784,G,09:12:19.50,-24:10:21.4,Hya -NGC2785,G,09:15:15.39,+40:55:03.1,Lyn -NGC2786,G,09:13:35.59,+12:26:26.9,Cnc -NGC2787,G,09:19:18.60,+69:12:11.7,UMa -NGC2788,G,09:09:03.04,-67:55:56.9,Car -NGC2788A,G,09:02:39.44,-68:13:36.6,Vol -NGC2788B,G,09:03:35.70,-67:58:11.3,Vol -NGC2789,G,09:14:59.66,+29:43:49.0,Cnc -NGC2790,G,09:15:02.78,+19:41:49.6,Cnc -NGC2791,G,09:15:01.99,+17:35:32.0,Cnc -NGC2792,PN,09:12:26.57,-42:25:39.0,Vel -NGC2793,G,09:16:47.31,+34:25:47.3,Lyn -NGC2794,G,09:16:01.79,+17:35:23.4,Cnc -NGC2795,G,09:16:03.92,+17:37:42.1,Cnc -NGC2796,G,09:16:41.85,+30:54:55.5,Cnc -NGC2797,G,09:16:21.69,+17:43:38.0,Cnc -NGC2798,G,09:17:22.79,+41:59:59.0,Lyn -NGC2799,G,09:17:31.03,+41:59:38.7,Lyn -NGC2800,G,09:18:35.22,+52:30:52.1,UMa -NGC2801,G,09:16:44.19,+19:56:08.6,Cnc -NGC2802,G,09:16:41.43,+18:57:48.5,Cnc -NGC2803,G,09:16:43.87,+18:57:16.5,Cnc -NGC2804,G,09:16:50.01,+20:11:54.6,Cnc -NGC2805,G,09:20:20.41,+64:06:10.0,UMa -NGC2806,*,09:16:56.79,+20:04:14.2,Cnc -NGC2807,GPair,09:16:59.00,+20:01:56.0,Cnc -NGC2807A,G,09:16:57.68,+20:01:44.6,Cnc -NGC2807B,G,09:17:00.66,+20:02:11.4,Cnc -NGC2808,GCl,09:12:02.54,-64:51:46.2,Car -NGC2809,G,09:17:06.92,+20:04:10.9,Cnc -NGC2810,G,09:22:04.50,+71:50:38.5,UMa -NGC2811,G,09:16:11.11,-16:18:45.8,Hya -NGC2812,G,09:17:40.79,+19:55:08.0,Cnc -NGC2813,G,09:17:45.45,+19:54:23.8,Cnc -NGC2814,G,09:21:11.48,+64:15:11.5,UMa -NGC2815,G,09:16:19.75,-23:37:59.7,Hya -NGC2816,Dup,09:21:45.58,+64:15:28.6,UMa -NGC2817,G,09:17:10.53,-04:45:07.6,Hya -NGC2818,PN,09:16:01.50,-36:37:37.0,Pyx -NGC2818A,Cl+N,09:16:06.13,-36:37:36.9,Pyx -NGC2819,G,09:18:09.29,+16:11:53.2,Cnc -NGC2820,G,09:21:45.58,+64:15:28.6,UMa -NGC2820A,G,09:21:30.07,+64:14:19.4,UMa -NGC2821,G,09:16:47.98,-26:48:58.6,Pyx -NGC2822,G,09:13:49.72,-69:38:41.4,Car -NGC2823,G,09:19:17.46,+34:00:29.4,Lyn -NGC2824,G,09:19:02.23,+26:16:11.9,Cnc -NGC2825,G,09:19:22.44,+33:44:34.0,Lyn -NGC2826,G,09:19:24.17,+33:37:26.4,Lyn -NGC2827,G,09:19:19.01,+33:52:50.9,Lyn -NGC2828,G,09:19:34.83,+33:53:17.1,Lyn -NGC2829,GPair,09:19:30.70,+33:38:54.0,Lyn -NGC2829 NED01,G,09:19:30.31,+33:38:54.5,Lyn -NGC2829 NED02,G,09:19:31.14,+33:38:52.0,Lyn -NGC2830,G,09:19:41.41,+33:44:17.3,Lyn -NGC2831,G,09:19:45.49,+33:44:42.0,Lyn -NGC2832,G,09:19:46.86,+33:44:59.1,Lyn -NGC2833,G,09:19:57.86,+33:55:38.8,Lyn -NGC2834,G,09:20:02.52,+33:42:37.7,Lyn -NGC2835,G,09:17:52.91,-22:21:16.8,Hya -NGC2836,G,09:13:44.60,-69:20:05.1,Car -NGC2837,**,09:18:23.37,-16:28:54.2,Hya -NGC2838,G,09:20:43.04,+39:18:56.7,Lyn -NGC2839,G,09:20:36.33,+33:39:02.4,Lyn -NGC2840,G,09:20:52.72,+35:22:05.8,Lyn -NGC2841,G,09:22:02.63,+50:58:35.5,UMa -NGC2842,G,09:15:36.43,-63:04:10.7,Car -NGC2843,G,09:20:28.78,+18:55:34.4,Cnc -NGC2844,G,09:21:48.01,+40:09:04.5,Lyn -NGC2845,G,09:18:36.71,-38:00:36.3,Vel -NGC2846,**,09:19:40.47,-14:40:34.6,Hya -NGC2847,HII,09:20:08.53,-16:31:05.9,Hya -NGC2848,G,09:20:09.83,-16:31:33.8,Hya -NGC2849,OCl,09:19:22.87,-40:31:13.4,Vel -NGC2850,G,09:20:57.01,-04:56:24.3,Hya -NGC2851,G,09:20:30.23,-16:29:43.0,Hya -NGC2852,G,09:23:14.59,+40:09:49.7,Lyn -NGC2853,G,09:23:17.33,+40:12:00.2,Lyn -NGC2854,G,09:24:03.11,+49:12:14.9,UMa -NGC2855,G,09:21:27.49,-11:54:34.2,Hya -NGC2856,G,09:24:16.01,+49:14:57.1,UMa -NGC2857,G,09:24:37.72,+49:21:25.4,UMa -NGC2858,G,09:22:55.01,+03:09:25.0,Hya -NGC2859,G,09:24:18.53,+34:30:48.6,LMi -NGC2860,G,09:24:53.21,+41:03:36.7,Lyn -NGC2861,G,09:23:36.50,+02:08:11.3,Hya -NGC2862,G,09:24:55.11,+26:46:28.8,Leo -NGC2863,G,09:23:36.56,-10:25:59.6,Hya -NGC2864,G,09:24:15.39,+05:56:28.2,Hya -NGC2865,G,09:23:30.21,-23:09:41.2,Hya -NGC2866,OCl,09:22:05.04,-51:06:09.4,Vel -NGC2867,PN,09:21:24.97,-58:18:42.0,Car -NGC2868,G,09:23:27.22,-10:25:46.2,Hya -NGC2869,Dup,09:23:36.56,-10:25:59.6,Hya -NGC2870,G,09:27:53.74,+57:22:31.8,UMa -NGC2871,*,09:25:39.54,+11:26:39.6,Leo -NGC2872,G,09:25:42.54,+11:25:55.7,Leo -NGC2873,G,09:25:48.50,+11:27:15.2,Leo -NGC2874,G,09:25:47.33,+11:25:28.5,Leo -NGC2875,Other,09:25:48.81,+11:25:53.9,Leo -NGC2876,G,09:25:13.77,-06:42:59.7,Hya -NGC2877,G,09:25:46.98,+02:13:44.8,Hya -NGC2878,G,09:25:47.46,+02:05:22.6,Hya -NGC2879,Other,09:25:22.52,-11:39:05.4,Hya -NGC2880,G,09:29:34.56,+62:29:26.0,UMa -NGC2881,GPair,09:25:54.20,-11:59:38.0,Hya -NGC2881 NED01,G,09:25:53.75,-11:59:31.7,Hya -NGC2881 NED02,G,09:25:54.69,-11:59:43.9,Hya -NGC2882,G,09:26:36.14,+07:57:16.1,Leo -NGC2883,G,09:25:18.42,-34:06:11.7,Pyx -NGC2884,G,09:26:24.45,-11:33:20.1,Hya -NGC2885,G,09:27:18.51,+23:01:12.4,Leo -NGC2886,Other,09:26:38.70,-21:44:16.1,Hya -NGC2887,G,09:23:24.05,-63:48:45.2,Car -NGC2888,G,09:26:19.67,-28:02:06.3,Pyx -NGC2889,G,09:27:12.59,-11:38:36.3,Hya -NGC2890,G,09:26:29.84,-14:31:43.3,Hya -NGC2891,G,09:26:56.63,-24:46:58.9,Pyx -NGC2892,G,09:32:52.93,+67:37:02.6,UMa -NGC2893,G,09:30:16.96,+29:32:23.9,Leo -NGC2894,G,09:29:30.24,+07:43:07.8,Leo -NGC2895,G,09:32:25.05,+57:28:58.4,UMa -NGC2896,G,09:30:16.97,+23:39:47.0,Leo -NGC2897,G,09:29:45.72,+02:12:24.5,Hya -NGC2898,G,09:29:46.34,+02:03:51.7,Hya -NGC2899,PN,09:27:02.96,-56:06:21.7,Vel -NGC2900,G,09:30:15.19,+04:08:39.2,Hya -NGC2901,Other,09:32:34.25,+31:06:42.1,Leo -NGC2902,G,09:30:52.89,-14:44:08.8,Hya -NGC2903,G,09:32:10.11,+21:30:03.0,Leo -NGC2904,G,09:30:17.00,-30:23:06.1,Ant -NGC2905,Dup,09:32:10.11,+21:30:03.0,Leo -NGC2906,G,09:32:06.22,+08:26:30.4,Leo -NGC2907,G,09:31:36.72,-16:44:04.8,Hya -NGC2908,G,09:43:31.45,+79:42:04.5,Dra -NGC2909,**,09:36:59.90,+65:56:26.0,UMa -NGC2910,OCl,09:30:29.02,-52:54:50.4,Vel -NGC2911,G,09:33:46.11,+10:09:08.8,Leo -NGC2912,*,09:33:56.88,+10:11:32.0,Leo -NGC2913,G,09:34:02.72,+09:28:45.1,Leo -NGC2914,G,09:34:02.78,+10:06:30.9,Leo -NGC2915,G,09:26:11.53,-76:37:34.8,Cha -NGC2916,G,09:34:57.60,+21:42:19.0,Leo -NGC2917,G,09:34:26.90,-02:30:15.0,Hya -NGC2918,G,09:35:44.04,+31:42:19.7,Leo -NGC2919,G,09:34:47.52,+10:17:01.4,Leo -NGC2920,G,09:34:12.25,-20:51:32.6,Hya -NGC2921,G,09:34:31.60,-20:55:13.3,Hya -NGC2922,G,09:36:52.47,+37:41:41.6,LMi -NGC2923,G,09:36:03.83,+16:45:37.4,Leo -NGC2924,G,09:35:10.81,-16:23:54.2,Hya -NGC2925,OCl,09:33:10.92,-53:23:45.5,Vel -NGC2926,G,09:37:31.01,+32:50:29.1,Leo -NGC2927,G,09:37:15.20,+23:35:26.2,Leo -NGC2928,G,09:37:10.08,+16:58:37.9,Leo -NGC2929,G,09:37:29.81,+23:09:42.0,Leo -NGC2930,G,09:37:32.60,+23:12:09.0,Leo -NGC2931,G,09:37:37.65,+23:14:26.7,Leo -NGC2932,*Ass,09:35:51.62,-46:55:28.2,Vel -NGC2933,G,09:37:55.00,+17:00:53.0,Leo -NGC2934,G,09:37:55.16,+17:03:16.2,Leo -NGC2935,G,09:36:44.85,-21:07:41.3,Hya -NGC2936,G,09:37:44.15,+02:45:38.9,Hya -NGC2937,G,09:37:45.03,+02:44:50.5,Hya -NGC2938,G,09:38:23.79,+76:19:10.4,Dra -NGC2939,G,09:38:08.07,+09:31:17.5,Leo -NGC2940,G,09:38:05.19,+09:37:00.2,Leo -NGC2941,G,09:38:24.21,+17:02:40.0,Leo -NGC2942,G,09:39:07.96,+34:00:22.8,LMi -NGC2943,G,09:38:32.87,+17:01:52.7,Leo -NGC2944,GPair,09:39:18.20,+32:18:30.0,Leo -NGC2944 NED01,G,09:39:18.12,+32:18:37.6,Leo -NGC2944 NED02,G,09:39:19.61,+32:18:21.6,Leo -NGC2945,G,09:37:41.13,-22:02:06.2,Hya -NGC2946,G,09:39:01.58,+17:01:31.2,Leo -NGC2947,G,09:36:05.79,-12:26:12.2,Hya -NGC2948,G,09:38:59.19,+06:57:19.6,Leo -NGC2949,GPair,09:39:56.25,+16:47:14.8,Leo -NGC2950,G,09:42:35.15,+58:51:04.6,UMa -NGC2951,GPair,09:39:40.40,-00:14:07.0,Hya -NGC2952,G,09:37:36.99,-10:11:00.2,Hya -NGC2953,Other,09:40:19.04,+14:49:57.7,Leo -NGC2954,G,09:40:24.07,+14:55:21.5,Leo -NGC2955,G,09:41:16.62,+35:52:56.2,LMi -NGC2956,G,09:39:17.05,-19:06:03.9,Hya -NGC2957,GPair,09:47:16.90,+72:59:06.0,Dra -NGC2957 NED01,G,09:47:15.77,+72:59:09.8,Dra -NGC2957A,G,09:47:18.00,+72:59:02.7,Dra -NGC2958,G,09:40:41.62,+11:53:18.3,Leo -NGC2959,G,09:45:08.97,+68:35:40.5,UMa -NGC2960,G,09:40:36.38,+03:34:37.2,Hya -NGC2961,G,09:45:22.47,+68:36:29.8,UMa -NGC2962,G,09:40:53.93,+05:09:56.9,Hya -NGC2963,G,09:47:50.42,+72:57:51.8,Dra -NGC2964,G,09:42:54.23,+31:50:50.6,Leo -NGC2965,G,09:43:19.15,+36:14:52.1,LMi -NGC2966,G,09:42:11.48,+04:40:23.3,Sex -NGC2967,G,09:42:03.29,+00:20:11.2,Sex -NGC2968,G,09:43:12.01,+31:55:43.3,Leo -NGC2969,G,09:41:54.50,-08:36:10.8,Sex -NGC2970,G,09:43:31.07,+31:58:37.1,Leo -NGC2971,G,09:43:46.12,+36:10:45.7,LMi -NGC2972,OCl,09:40:11.52,-50:19:15.4,Vel -NGC2973,Other,09:41:34.75,-30:02:54.2,Ant -NGC2974,G,09:42:33.28,-03:41:56.9,Sex -NGC2975,G,09:41:16.07,-16:40:27.8,Hya -NGC2976,G,09:47:15.46,+67:54:59.0,UMa -NGC2977,G,09:43:46.29,+74:51:36.8,Dra -NGC2978,G,09:43:16.80,-09:44:45.2,Sex -NGC2979,G,09:43:08.65,-10:22:59.7,Sex -NGC2980,G,09:43:11.97,-09:36:44.6,Sex -NGC2981,G,09:44:56.57,+31:05:52.2,Leo -NGC2982,OCl,09:42:00.08,-44:01:37.7,Vel -NGC2983,G,09:43:41.10,-20:28:37.9,Hya -NGC2984,G,09:43:40.38,+11:03:39.1,Leo -NGC2985,G,09:50:22.23,+72:16:43.1,UMa -NGC2986,G,09:44:16.04,-21:16:40.8,Hya -NGC2987,G,09:45:41.47,+04:56:30.6,Sex -NGC2988,G,09:46:47.97,+22:00:43.7,Leo -NGC2989,G,09:45:25.22,-18:22:26.1,Hya -NGC2990,G,09:46:17.16,+05:42:31.7,Sex -NGC2991,G,09:46:50.12,+22:00:50.2,Leo -NGC2992,G,09:45:42.05,-14:19:35.0,Hya -NGC2993,G,09:45:48.33,-14:22:05.9,Hya -NGC2994,G,09:47:16.11,+22:05:22.2,Leo -NGC2995,*Ass,09:43:59.09,-54:35:49.0,Vel -NGC2996,G,09:46:30.18,-21:34:17.8,Hya -NGC2997,G,09:45:38.79,-31:11:27.9,Ant -NGC2998,G,09:48:43.63,+44:04:53.2,UMa -NGC2999,Dup,09:40:11.52,-50:19:15.4,Vel -NGC3000,**,09:48:51.28,+44:07:49.0,UMa -NGC3001,G,09:46:18.66,-30:26:14.9,Ant -NGC3002,*,09:48:57.42,+44:03:26.0,UMa -NGC3003,G,09:48:36.05,+33:25:17.4,LMi -NGC3004,*,09:49:02.45,+44:06:39.7,UMa -NGC3005,G,09:49:14.87,+44:07:52.7,UMa -NGC3006,G,09:49:17.34,+44:01:32.9,UMa -NGC3007,G,09:47:45.62,-06:26:17.4,Sex -NGC3008,G,09:49:34.26,+44:06:09.7,UMa -NGC3009,G,09:50:11.13,+44:17:42.2,UMa -NGC3010,G,09:50:33.15,+44:18:51.8,UMa -NGC3010A,G,09:50:34.57,+44:19:24.3,UMa -NGC3010C,G,09:50:39.39,+44:19:51.5,UMa -NGC3011,G,09:49:41.20,+32:13:16.0,Leo -NGC3012,G,09:49:52.11,+34:42:50.8,LMi -NGC3013,G,09:50:09.36,+33:34:09.6,LMi -NGC3014,G,09:49:07.65,-04:44:34.6,Sex -NGC3015,G,09:49:22.92,+01:08:43.5,Sex -NGC3016,G,09:49:50.66,+12:41:42.8,Leo -NGC3017,G,09:49:03.04,-02:49:18.5,Sex -NGC3018,G,09:49:41.45,+00:37:16.5,Sex -NGC3019,G,09:50:07.21,+12:44:46.1,Leo -NGC3020,G,09:50:06.60,+12:48:49.1,Leo -NGC3021,G,09:50:57.15,+33:33:13.1,LMi -NGC3022,G,09:49:39.25,-05:09:59.7,Sex -NGC3023,G,09:49:52.59,+00:37:05.4,Sex -NGC3024,G,09:50:27.39,+12:45:55.8,Leo -NGC3025,G,09:49:29.06,-21:44:32.1,Hya -NGC3026,G,09:50:55.37,+28:33:04.0,Leo -NGC3027,G,09:55:40.60,+72:12:12.8,UMa -NGC3028,G,09:49:54.24,-19:11:03.0,Hya -NGC3029,G,09:48:54.02,-08:03:03.4,Sex -NGC3030,G,09:50:10.52,-12:13:35.1,Hya -NGC3031,G,09:55:33.17,+69:03:55.1,UMa -NGC3032,G,09:52:08.15,+29:14:10.4,Leo -NGC3033,OCl,09:48:35.04,-56:25:48.2,Vel -NGC3034,G,09:55:52.73,+69:40:45.8,UMa -NGC3035,G,09:51:55.03,-06:49:22.5,Sex -NGC3036,OCl,09:49:15.89,-62:40:32.1,Car -NGC3037,G,09:51:24.02,-27:00:40.0,Hya -NGC3038,G,09:51:15.45,-32:45:09.2,Ant -NGC3039,G,09:52:29.67,+02:09:16.0,Sex -NGC3040,GPair,09:53:04.30,+19:26:14.0,Leo -NGC3040 NED01,G,09:53:03.46,+19:26:33.3,Leo -NGC3040 NED02,G,09:53:05.10,+19:25:55.8,Leo -NGC3041,G,09:53:07.14,+16:40:39.6,Leo -NGC3042,G,09:53:20.22,+00:41:51.7,Sex -NGC3043,G,09:56:14.85,+59:18:25.6,UMa -NGC3044,G,09:53:40.88,+01:34:46.7,Sex -NGC3045,G,09:53:17.66,-18:38:42.4,Hya -NGC3046,Dup,09:53:58.64,-27:17:10.8,Ant -NGC3047,GPair,09:54:30.70,-01:17:23.0,Sex -NGC3047A,G,09:54:32.06,-01:17:27.3,Sex -NGC3047B,G,09:54:29.49,-01:17:17.0,Sex -NGC3048,GPair,09:54:57.20,+16:27:28.0,Leo -NGC3048 NED01,G,09:54:56.45,+16:27:22.6,Leo -NGC3048 NED02,G,09:54:58.04,+16:27:34.5,Leo -NGC3049,G,09:54:49.56,+09:16:15.9,Leo -NGC3050,Dup,09:43:08.65,-10:22:59.7,Sex -NGC3051,G,09:53:58.64,-27:17:10.8,Ant -NGC3052,G,09:54:27.93,-18:38:20.0,Hya -NGC3053,G,09:55:33.61,+16:25:58.3,Leo -NGC3054,G,09:54:28.60,-25:42:12.4,Hya -NGC3055,G,09:55:18.06,+04:16:12.1,Sex -NGC3056,G,09:54:32.88,-28:17:53.5,Ant -NGC3057,G,10:05:39.36,+80:17:08.5,Dra -NGC3058,GPair,09:53:35.75,-12:28:53.6,Hya -NGC3058 NED01,G,09:53:35.15,-12:28:44.7,Hya -NGC3058 NED02,G,09:53:36.17,-12:28:55.6,Hya -NGC3059,G,09:50:08.16,-73:55:19.9,Car -NGC3060,G,09:56:19.20,+16:49:52.4,Leo -NGC3061,G,09:56:12.01,+75:51:59.4,Dra -NGC3062,G,09:56:35.79,+01:25:42.8,Sex -NGC3063,**,10:01:41.77,+72:07:04.2,UMa -NGC3064,G,09:55:41.45,-06:21:50.1,Sex -NGC3065,G,10:01:55.22,+72:10:13.2,UMa -NGC3066,G,10:02:11.07,+72:07:31.4,UMa -NGC3067,G,09:58:21.08,+32:22:11.6,Leo -NGC3068,G,09:58:40.10,+28:52:39.2,Leo -NGC3069,G,09:57:56.70,+10:25:56.8,Leo -NGC3070,G,09:58:06.95,+10:21:35.2,Leo -NGC3071,G,09:58:52.99,+31:37:13.1,Leo -NGC3072,G,09:57:23.89,-19:21:17.9,Hya -NGC3073,G,10:00:52.08,+55:37:07.8,UMa -NGC3074,G,09:59:41.21,+35:23:34.1,LMi -NGC3075,G,09:58:56.20,+14:25:13.7,Leo -NGC3076,G,09:57:37.62,-18:10:43.3,Hya -NGC3077,G,10:03:19.07,+68:44:02.1,UMa -NGC3078,G,09:58:24.61,-26:55:36.0,Hya -NGC3079,G,10:01:57.80,+55:40:47.2,UMa -NGC3080,G,09:59:55.84,+13:02:37.8,Leo -NGC3081,G,09:59:29.54,-22:49:34.6,Hya -NGC3082,G,09:58:53.07,-30:21:27.7,Ant -NGC3083,G,09:59:49.67,-02:52:38.9,Sex -NGC3084,G,09:59:06.42,-27:07:43.7,Ant -NGC3085,G,09:59:29.18,-19:29:32.2,Hya -NGC3086,G,10:00:10.99,-02:58:34.2,Sex -NGC3087,G,09:59:08.66,-34:13:30.8,Ant -NGC3088,GPair,10:01:09.10,+22:24:13.0,Leo -NGC3088A,G,10:01:07.19,+22:24:11.4,Leo -NGC3088B,G,10:01:09.70,+22:24:08.3,Leo -NGC3089,G,09:59:36.68,-28:19:52.9,Ant -NGC3090,G,10:00:30.23,-02:58:08.4,Sex -NGC3091,G,10:00:14.29,-19:38:13.1,Hya -NGC3092,G,10:00:47.43,-03:00:44.6,Sex -NGC3093,G,10:00:53.59,-02:58:19.2,Sex -NGC3094,G,10:01:25.94,+15:46:12.3,Leo -NGC3095,G,10:00:05.83,-31:33:10.3,Ant -NGC3096,G,10:00:33.11,-19:39:43.1,Hya -NGC3097,Other,10:04:16.07,+60:07:32.8,UMa -NGC3098,G,10:02:16.69,+24:42:39.9,Leo -NGC3099,G,10:02:36.55,+32:42:24.2,LMi -NGC3100,G,10:00:40.84,-31:39:52.3,Ant -NGC3101,G,10:01:35.43,-02:59:39.9,Sex -NGC3102,G,10:04:31.76,+60:06:28.7,UMa -NGC3103,Dup,10:00:40.84,-31:39:52.3,Ant -NGC3104,G,10:03:57.35,+40:45:24.9,LMi -NGC3105,OCl,10:00:39.52,-54:47:15.7,Vel -NGC3106,G,10:04:05.25,+31:11:07.7,LMi -NGC3107,G,10:04:22.47,+13:37:16.8,Leo -NGC3108,G,10:02:29.03,-31:40:38.7,Ant -NGC3109,G,10:03:06.88,-26:09:34.5,Hya -NGC3110,G,10:04:02.11,-06:28:29.2,Sex -NGC3111,G,10:06:07.44,+47:15:45.5,UMa -NGC3112,G,10:03:59.18,-20:46:54.5,Hya -NGC3113,G,10:04:26.11,-28:26:38.5,Ant -NGC3114,OCl,10:02:29.57,-60:07:49.9,Car -NGC3115,G,10:05:13.98,-07:43:06.9,Sex -NGC3116,G,10:06:45.05,+31:05:51.8,LMi -NGC3117,G,10:06:10.50,+02:54:46.3,Sex -NGC3118,G,10:07:11.54,+33:01:38.6,LMi -NGC3119,G,10:06:51.87,+14:22:24.7,Leo -NGC3120,G,10:05:23.04,-34:13:11.8,Ant -NGC3121,Dup,10:06:51.87,+14:22:24.7,Leo -NGC3122,Dup,10:04:02.11,-06:28:29.2,Sex -NGC3123,Other,10:07:01.85,+00:04:01.7,Sex -NGC3124,G,10:06:39.90,-19:13:17.7,Hya -NGC3125,G,10:06:33.37,-29:56:05.5,Ant -NGC3126,G,10:08:20.66,+31:51:45.7,LMi -NGC3127,G,10:06:24.85,-16:07:33.9,Hya -NGC3128,G,10:06:01.37,-16:07:19.3,Hya -NGC3129,**,10:08:19.26,+18:25:50.3,Leo -NGC3130,G,10:08:12.34,+09:58:37.2,Leo -NGC3131,G,10:08:36.41,+18:13:52.4,Leo -NGC3132,PN,10:07:01.73,-40:26:11.7,Vel -NGC3133,G,10:07:12.81,-11:57:55.1,Hya -NGC3134,G,10:12:29.29,+12:22:38.1,Leo -NGC3135,G,10:10:54.38,+45:57:01.3,UMa -NGC3136,G,10:05:48.16,-67:22:40.7,Car -NGC3136A,G,10:03:32.58,-67:26:54.5,Car -NGC3136B,G,10:10:12.93,-67:00:18.3,Car -NGC3137,G,10:09:07.48,-29:03:51.5,Ant -NGC3138,G,10:09:16.67,-11:57:24.3,Hya -NGC3139,G,10:10:05.18,-11:46:41.7,Hya -NGC3140,G,10:09:27.82,-16:37:41.4,Hya -NGC3141,G,10:09:19.89,-16:39:12.3,Hya -NGC3142,G,10:10:06.37,-08:28:47.1,Sex -NGC3143,G,10:10:03.98,-12:34:52.9,Hya -NGC3144,G,10:15:32.18,+74:13:13.0,Dra -NGC3145,G,10:10:09.87,-12:26:01.6,Hya -NGC3146,G,10:11:09.87,-20:52:14.5,Hya -NGC3147,G,10:16:53.65,+73:24:02.7,Dra -NGC3148,*,10:13:43.83,+50:29:47.5,UMa -NGC3149,G,10:03:44.09,-80:25:18.0,Cha -NGC3150,G,10:13:26.32,+38:39:27.6,LMi -NGC3151,G,10:13:29.09,+38:37:11.4,LMi -NGC3152,G,10:13:34.12,+38:50:35.5,LMi -NGC3153,G,10:12:50.50,+12:40:00.0,Leo -NGC3154,G,10:13:01.28,+17:02:03.0,Leo -NGC3155,G,10:17:39.85,+74:20:50.6,Dra -NGC3156,G,10:12:41.25,+03:07:45.7,Sex -NGC3157,G,10:11:42.43,-31:38:34.3,Ant -NGC3158,G,10:13:50.52,+38:45:53.6,LMi -NGC3159,G,10:13:52.83,+38:39:16.1,LMi -NGC3160,G,10:13:55.07,+38:50:34.2,LMi -NGC3161,G,10:13:59.20,+38:39:25.7,LMi -NGC3162,G,10:13:31.59,+22:44:15.2,Leo -NGC3163,G,10:14:07.10,+38:39:09.2,LMi -NGC3164,G,10:15:11.42,+56:40:19.5,UMa -NGC3165,G,10:13:31.30,+03:22:30.1,Sex -NGC3166,G,10:13:45.60,+03:25:29.0,Sex -NGC3167,Other,10:14:35.89,+29:35:46.8,LMi -NGC3168,G,10:16:23.01,+60:14:05.8,UMa -NGC3169,G,10:14:15.05,+03:27:57.9,Sex -NGC3170,**,10:16:14.53,+46:36:44.5,UMa -NGC3171,G,10:15:36.82,-20:38:50.6,Hya -NGC3172,G,11:47:14.00,+89:05:35.0,UMi -NGC3173,G,10:14:34.87,-27:41:34.2,Ant -NGC3174,Dup,10:15:32.18,+74:13:13.0,Dra -NGC3175,G,10:14:42.11,-28:52:19.4,Ant -NGC3176,Other,10:15:17.54,-19:01:57.2,Hya -NGC3177,G,10:16:34.14,+21:07:23.0,Leo -NGC3178,G,10:16:09.17,-15:47:28.6,Hya -NGC3179,G,10:17:57.21,+41:06:51.5,UMa -NGC3180,HII,10:18:09.67,+41:26:42.1,UMa -NGC3181,HII,10:18:11.51,+41:24:45.7,UMa -NGC3182,G,10:19:33.02,+58:12:20.6,UMa -NGC3183,G,10:21:49.00,+74:10:36.7,Dra -NGC3184,G,10:18:16.86,+41:25:26.6,UMa -NGC3185,G,10:17:38.57,+21:41:17.7,Leo -NGC3186,G,10:15:53.39,+06:57:49.5,Leo -NGC3187,G,10:17:47.86,+21:52:24.0,Leo -NGC3188,G,10:19:42.81,+57:25:24.5,UMa -NGC3188A,G,10:19:38.28,+57:25:06.9,UMa -NGC3189,G,10:18:05.63,+21:49:56.3,Leo -NGC3190,Dup,10:18:05.63,+21:49:56.3,Leo -NGC3191,G,10:19:05.13,+46:27:14.8,UMa -NGC3192,Dup,10:19:05.13,+46:27:14.8,UMa -NGC3193,G,10:18:24.90,+21:53:38.3,Leo -NGC3194,Dup,10:17:39.85,+74:20:50.6,Dra -NGC3195,PN,10:09:20.98,-80:51:30.9,Cha -NGC3196,G,10:18:49.03,+27:40:08.2,Leo -NGC3197,G,10:14:27.67,+77:49:12.9,Dra -NGC3198,G,10:19:54.95,+45:32:58.6,UMa -NGC3199,Neb,10:17:24.43,-57:55:20.0,Car -NGC3200,G,10:18:36.55,-17:58:57.1,Hya -NGC3201,GCl,10:17:36.76,-46:24:40.4,Vel -NGC3202,G,10:20:31.74,+43:01:17.8,UMa -NGC3203,G,10:19:33.83,-26:41:56.0,Hya -NGC3204,G,10:20:11.08,+27:49:01.4,Leo -NGC3205,G,10:20:49.97,+42:58:19.4,UMa -NGC3206,G,10:21:47.59,+56:55:49.5,UMa -NGC3207,G,10:21:00.55,+42:59:07.1,UMa -NGC3208,G,10:19:41.29,-25:48:53.1,Hya -NGC3209,G,10:20:38.42,+25:30:17.9,Leo -NGC3210,**,10:27:59.20,+79:49:57.1,Dra -NGC3211,PN,10:17:50.51,-62:40:12.2,Car -NGC3212,G,10:28:16.46,+79:49:24.2,Dra -NGC3213,G,10:21:17.35,+19:39:06.4,Leo -NGC3214,G,10:23:08.79,+57:02:20.7,UMa -NGC3215,G,10:28:40.58,+79:48:47.1,Dra -NGC3216,G,10:21:41.23,+23:55:23.0,Leo -NGC3217,G,10:23:32.62,+10:57:35.0,Leo -NGC3218,Dup,10:21:49.00,+74:10:36.7,Dra -NGC3219,G,10:22:37.44,+38:34:45.0,LMi -NGC3220,G,10:23:44.66,+57:01:36.7,UMa -NGC3221,G,10:22:19.98,+21:34:10.5,Leo -NGC3222,G,10:22:34.51,+19:53:13.3,Leo -NGC3223,G,10:21:35.08,-34:16:00.5,Ant -NGC3224,G,10:21:41.19,-34:41:48.4,Ant -NGC3225,G,10:25:09.90,+58:09:00.0,UMa -NGC3226,G,10:23:27.01,+19:53:54.7,Leo -NGC3227,G,10:23:30.58,+19:51:54.2,Leo -NGC3228,OCl,10:21:22.24,-51:43:21.3,Vel -NGC3229,Other,10:23:24.36,+00:03:53.3,Sex -NGC3230,G,10:23:43.96,+12:34:04.0,Leo -NGC3231,OCl,10:26:57.85,+66:48:54.6,UMa -NGC3232,G,10:24:24.23,+28:01:34.1,LMi -NGC3233,G,10:21:57.46,-22:16:03.9,Hya -NGC3234,G,10:24:59.31,+28:01:26.1,LMi -NGC3235,Dup,10:24:59.31,+28:01:26.1,LMi -NGC3236,G,10:26:48.51,+61:16:22.5,UMa -NGC3237,G,10:25:43.30,+39:38:47.0,UMa -NGC3238,G,10:26:42.99,+57:13:34.8,UMa -NGC3239,G,10:25:04.89,+17:09:48.9,Leo -NGC3240,G,10:24:30.63,-21:47:27.7,Hya -NGC3241,G,10:24:16.96,-32:28:57.4,Ant -NGC3242,PN,10:24:46.08,-18:38:32.0,Hya -NGC3243,G,10:26:21.47,-02:37:19.9,Sex -NGC3244,G,10:25:28.84,-39:49:39.2,Ant -NGC3245,G,10:27:18.39,+28:30:26.8,LMi -NGC3245A,G,10:27:01.13,+28:38:21.6,LMi -NGC3246,G,10:26:41.81,+03:51:42.9,Sex -NGC3247,HII,10:24:14.00,-57:45:48.0,Car -NGC3248,G,10:27:45.44,+22:50:49.9,Leo -NGC3249,G,10:26:22.21,-34:57:49.3,Ant -NGC3250,G,10:26:32.28,-39:56:38.2,Ant -NGC3250A,G,10:27:53.73,-40:04:52.8,Ant -NGC3250B,G,10:27:44.77,-40:26:06.1,Vel -NGC3250C,G,10:27:42.47,-40:00:09.3,Ant -NGC3250D,G,10:27:57.94,-39:48:53.2,Ant -NGC3250E,G,10:29:00.70,-40:04:58.1,Ant -NGC3251,G,10:29:16.84,+26:05:57.3,Leo -NGC3252,G,10:34:23.14,+73:45:53.9,Dra -NGC3253,G,10:28:27.32,+12:42:14.6,Leo -NGC3254,G,10:29:19.94,+29:29:30.6,LMi -NGC3255,OCl,10:26:31.36,-60:40:30.7,Car -NGC3256,G,10:27:51.27,-43:54:13.5,Vel -NGC3256A,G,10:25:51.04,-43:44:53.1,Vel -NGC3256B,G,10:29:01.07,-44:24:10.4,Vel -NGC3256C,G,10:29:05.74,-43:50:57.4,Vel -NGC3257,G,10:28:47.12,-35:39:29.1,Ant -NGC3258,G,10:28:53.57,-35:36:19.9,Ant -NGC3258A,G,10:28:19.16,-35:27:16.1,Ant -NGC3258B,G,10:30:25.30,-35:33:48.8,Ant -NGC3258C,G,10:31:24.15,-35:13:13.7,Ant -NGC3258D,G,10:31:55.73,-35:24:35.3,Ant -NGC3258E,G,10:32:24.91,-34:59:54.1,Ant -NGC3259,G,10:32:34.85,+65:02:27.9,UMa -NGC3260,G,10:29:06.39,-35:35:42.7,Ant -NGC3261,G,10:29:01.46,-44:39:24.6,Vel -NGC3262,G,10:29:06.23,-44:09:34.8,Vel -NGC3263,G,10:29:13.36,-44:07:22.5,Vel -NGC3264,G,10:32:19.70,+56:05:07.0,UMa -NGC3265,G,10:31:06.77,+28:47:48.0,LMi -NGC3266,G,10:33:17.61,+64:44:57.8,UMa -NGC3267,G,10:29:48.59,-35:19:20.6,Ant -NGC3268,G,10:30:00.66,-35:19:31.7,Ant -NGC3269,G,10:29:57.06,-35:13:27.8,Ant -NGC3270,G,10:31:29.98,+24:52:10.0,Leo -NGC3271,G,10:30:26.49,-35:21:34.2,Ant -NGC3272,**,10:31:48.15,+28:28:07.6,LMi -NGC3273,G,10:30:29.17,-35:36:38.3,Ant -NGC3274,G,10:32:17.27,+27:40:07.6,Leo -NGC3275,G,10:30:51.80,-36:44:13.1,Ant -NGC3276,G,10:31:09.20,-39:56:40.8,Ant -NGC3277,G,10:32:55.45,+28:30:42.2,LMi -NGC3278,G,10:31:35.39,-39:57:16.7,Ant -NGC3279,G,10:34:42.80,+11:11:50.4,Leo -NGC3280,GTrpl,10:32:45.27,-12:38:10.5,Hya -NGC3280A,G,10:32:46.38,-12:38:04.5,Hya -NGC3280B,G,10:32:43.81,-12:38:14.3,Hya -NGC3280C,G,10:32:45.55,-12:38:14.0,Hya -NGC3281,G,10:31:52.09,-34:51:13.3,Ant -NGC3281A,G,10:31:58.28,-35:11:54.6,Ant -NGC3281B,G,10:31:52.16,-35:12:17.5,Ant -NGC3281C,G,10:32:59.55,-34:53:10.4,Ant -NGC3281D,G,10:34:19.02,-34:24:12.3,Ant -NGC3282,G,10:32:21.92,-22:18:08.2,Hya -NGC3283,G,10:31:11.60,-46:15:04.6,Vel -NGC3284,G,10:36:21.24,+58:37:12.6,UMa -NGC3285,G,10:33:35.84,-27:27:16.0,Hya -NGC3285A,G,10:32:48.78,-27:31:20.8,Hya -NGC3285B,G,10:34:36.88,-27:39:10.6,Hya -NGC3286,Dup,10:36:21.24,+58:37:12.6,UMa -NGC3287,G,10:34:47.30,+21:38:53.8,Leo -NGC3288,G,10:36:25.67,+58:33:22.3,UMa -NGC3289,G,10:34:07.43,-35:19:24.2,Ant -NGC3290,G,10:35:17.43,-17:16:36.4,Hya -NGC3291,*,10:36:06.48,+37:16:27.9,LMi -NGC3292,G,10:35:34.43,-06:10:46.6,Sex -NGC3293,OCl,10:35:48.77,-58:13:28.1,Car -NGC3294,G,10:36:16.25,+37:19:28.9,LMi -NGC3295,Dup,10:32:45.27,-12:38:10.5,Hya -NGC3296,G,10:32:45.40,-12:43:02.7,Hya -NGC3297,G,10:33:11.80,-12:40:18.6,Hya -NGC3298,G,10:37:12.28,+50:07:14.1,UMa -NGC3299,G,10:36:23.80,+12:42:26.6,Leo -NGC3300,G,10:36:38.44,+14:10:16.0,Leo -NGC3301,G,10:36:56.04,+21:52:55.7,Leo -NGC3302,G,10:35:47.43,-32:21:30.6,Ant -NGC3303,GPair,10:36:59.80,+18:08:12.0,Leo -NGC3303 NED01,G,10:36:59.44,+18:08:15.7,Leo -NGC3303 NED02,G,10:37:00.01,+18:08:09.2,Leo -NGC3304,G,10:37:37.91,+37:27:20.3,LMi -NGC3305,G,10:36:11.75,-27:09:43.9,Hya -NGC3306,G,10:37:10.22,+12:39:08.9,Leo -NGC3307,G,10:36:17.17,-27:31:47.1,Hya -NGC3308,G,10:36:22.40,-27:26:17.3,Hya -NGC3309,G,10:36:35.70,-27:31:06.4,Hya -NGC3310,G,10:38:45.86,+53:30:12.2,UMa -NGC3311,G,10:36:42.82,-27:31:42.0,Hya -NGC3312,G,10:37:02.52,-27:33:54.2,Hya -NGC3313,G,10:37:25.45,-25:19:10.0,Hya -NGC3314,GPair,10:37:13.20,-27:41:04.0,Hya -NGC3314A,G,10:37:12.85,-27:41:02.2,Hya -NGC3314B,G,10:37:13.25,-27:41:05.9,Hya -NGC3315,G,10:37:19.23,-27:11:32.3,Hya -NGC3316,G,10:37:37.32,-27:35:39.5,Hya -NGC3317,Other,10:37:43.11,-27:31:10.6,Hya -NGC3318,G,10:37:15.51,-41:37:39.2,Vel -NGC3318A,G,10:35:32.10,-41:44:27.4,Vel -NGC3318B,G,10:37:33.84,-41:27:55.4,Vel -NGC3319,G,10:39:09.46,+41:41:12.0,UMa -NGC3320,G,10:39:36.53,+47:23:52.5,UMa -NGC3321,G,10:38:50.56,-11:38:55.9,Sex -NGC3322,Dup,10:38:50.56,-11:38:55.9,Sex -NGC3323,G,10:39:39.04,+25:19:21.9,LMi -NGC3324,Cl+N,10:37:16.21,-58:37:10.4,Car -NGC3325,G,10:39:20.45,-00:12:00.9,Sex -NGC3326,G,10:39:31.86,+05:06:27.4,Sex -NGC3327,G,10:39:57.94,+24:05:28.5,LMi -NGC3328,*,10:39:54.25,+09:18:00.4,Leo -NGC3329,G,10:44:39.37,+76:48:34.0,Dra -NGC3330,OCl,10:38:45.43,-54:07:50.6,Vel -NGC3331,G,10:40:08.94,-23:49:13.3,Hya -NGC3332,G,10:40:28.37,+09:10:57.2,Leo -NGC3333,G,10:39:49.83,-36:02:10.2,Ant -NGC3334,G,10:41:31.20,+37:18:46.3,LMi -NGC3335,G,10:39:34.06,-23:55:21.7,Hya -NGC3336,G,10:40:17.02,-27:46:37.5,Hya -NGC3337,G,10:41:47.59,+04:59:18.2,Sex -NGC3338,G,10:42:07.54,+13:44:49.2,Leo -NGC3339,*,10:42:10.06,-00:22:09.3,Sex -NGC3340,G,10:42:17.99,-00:22:36.7,Sex -NGC3341,G,10:42:31.47,+05:02:37.7,Sex -NGC3342,Dup,10:40:28.37,+09:10:57.2,Leo -NGC3343,G,10:46:10.45,+73:21:11.3,Dra -NGC3344,G,10:43:31.15,+24:55:20.0,LMi -NGC3345,**,10:43:32.02,+11:59:07.0,Leo -NGC3346,G,10:43:38.91,+14:52:18.7,Leo -NGC3347,G,10:42:46.55,-36:21:09.6,Ant -NGC3347A,G,10:40:20.61,-36:24:40.0,Ant -NGC3347B,G,10:41:59.82,-36:56:07.2,Ant -NGC3347C,G,10:40:53.69,-36:17:16.5,Ant -NGC3348,G,10:47:10.00,+72:50:22.8,UMa -NGC3349,G,10:43:50.56,+06:45:46.7,Leo -NGC3350,G,10:44:22.97,+30:43:29.5,LMi -NGC3351,G,10:43:57.70,+11:42:13.7,Leo -NGC3352,G,10:44:14.93,+22:22:16.3,Leo -NGC3353,G,10:45:22.41,+55:57:37.4,UMa -NGC3354,G,10:43:02.94,-36:21:44.6,Ant -NGC3355,G,10:41:25.99,-23:23:03.3,Hya -NGC3356,G,10:44:12.23,+06:45:31.6,Leo -NGC3357,G,10:44:20.74,+14:05:04.1,Leo -NGC3358,G,10:43:33.02,-36:24:38.5,Ant -NGC3359,G,10:46:36.86,+63:13:27.2,UMa -NGC3360,G,10:44:16.18,-11:14:33.2,Sex -NGC3361,G,10:44:29.17,-11:12:28.5,Sex -NGC3362,G,10:44:51.72,+06:35:48.2,Leo -NGC3363,G,10:45:09.46,+22:04:42.6,Leo -NGC3364,G,10:48:29.82,+72:25:30.1,UMa -NGC3365,G,10:46:12.59,+01:48:47.8,Sex -NGC3366,G,10:35:08.28,-43:41:30.5,Vel -NGC3367,G,10:46:34.95,+13:45:03.1,Leo -NGC3368,G,10:46:45.74,+11:49:11.8,Leo -NGC3369,G,10:46:44.65,-25:14:40.0,Hya -NGC3370,G,10:47:04.05,+17:16:25.0,Leo -NGC3371,Dup,10:48:16.89,+12:37:45.4,Leo -NGC3372,HII,10:45:08.53,-59:52:00.1,Car -NGC3373,Dup,10:48:27.91,+12:31:59.5,Leo -NGC3374,G,10:48:01.05,+43:11:11.6,UMa -NGC3375,G,10:47:00.79,-09:56:28.7,Sex -NGC3376,G,10:47:26.57,+06:02:53.3,Sex -NGC3377,G,10:47:42.33,+13:59:09.3,Leo -NGC3377A,G,10:47:22.30,+14:04:10.0,Leo -NGC3378,G,10:46:43.27,-40:00:57.7,Ant -NGC3379,G,10:47:49.59,+12:34:53.8,Leo -NGC3380,G,10:48:12.18,+28:36:06.4,LMi -NGC3381,G,10:48:24.82,+34:42:41.1,LMi -NGC3382,**,10:48:25.03,+36:43:31.6,LMi -NGC3383,G,10:47:19.20,-24:26:17.4,Hya -NGC3384,G,10:48:16.89,+12:37:45.4,Leo -NGC3385,G,10:48:11.63,+04:55:39.9,Sex -NGC3386,G,10:48:11.91,+04:59:54.9,Sex -NGC3387,Other,10:48:16.74,+04:57:59.8,Sex -NGC3388,Dup,10:51:25.53,+08:34:01.7,Leo -NGC3389,G,10:48:27.91,+12:31:59.5,Leo -NGC3390,G,10:48:04.36,-31:32:00.1,Hya -NGC3391,G,10:48:56.36,+14:13:11.4,Leo -NGC3392,G,10:51:03.00,+65:46:53.6,UMa -NGC3393,G,10:48:23.46,-25:09:43.4,Hya -NGC3394,G,10:50:39.84,+65:43:38.0,UMa -NGC3395,G,10:49:50.11,+32:58:58.3,LMi -NGC3396,G,10:49:55.07,+32:59:27.0,LMi -NGC3397,Dup,10:44:39.37,+76:48:34.0,Dra -NGC3398,G,10:51:31.43,+55:23:27.5,UMa -NGC3399,G,10:49:27.60,+16:13:06.8,Leo -NGC3400,G,10:50:45.47,+28:28:08.7,LMi -NGC3401,Other,10:50:19.87,+05:48:41.4,Sex -NGC3402,G,10:50:26.47,-12:50:40.6,Hya -NGC3403,G,10:53:54.86,+73:41:25.3,Dra -NGC3404,G,10:50:17.98,-12:06:31.4,Hya -NGC3405,GPair,10:49:43.80,+16:14:25.0,Leo -NGC3405 NED01,G,10:49:43.31,+16:14:19.8,Leo -NGC3405 NED02,G,10:49:44.36,+16:14:31.9,Leo -NGC3406,GPair,10:51:44.20,+51:01:26.0,UMa -NGC3406 NED01,G,10:51:43.63,+51:01:19.8,UMa -NGC3406 NED02,G,10:51:44.48,+51:01:30.3,UMa -NGC3407,G,10:52:17.79,+61:22:46.7,UMa -NGC3408,G,10:52:11.68,+58:26:17.3,UMa -NGC3409,G,10:50:20.35,-17:02:37.1,Hya -NGC3410,G,10:51:53.71,+51:00:23.4,UMa -NGC3411,Dup,10:50:26.47,-12:50:40.6,Hya -NGC3412,G,10:50:53.28,+13:24:43.7,Leo -NGC3413,G,10:51:20.74,+32:45:59.0,LMi -NGC3414,G,10:51:16.21,+27:58:30.4,LMi -NGC3415,G,10:51:42.60,+43:42:45.4,UMa -NGC3416,G,10:51:48.31,+43:45:50.9,UMa -NGC3417,G,10:51:01.72,+08:28:24.6,Leo -NGC3418,G,10:51:23.95,+28:06:43.3,LMi -NGC3419,G,10:51:17.74,+13:56:45.6,Leo -NGC3419A,G,10:51:19.94,+14:01:24.4,Leo -NGC3420,G,10:50:09.66,-17:14:33.1,Hya -NGC3421,G,10:50:57.63,-12:26:54.7,Hya -NGC3422,G,10:51:17.34,-12:24:08.6,Crt -NGC3423,G,10:51:14.33,+05:50:24.1,Sex -NGC3424,G,10:51:46.33,+32:54:02.7,LMi -NGC3425,G,10:51:25.53,+08:34:01.7,Leo -NGC3426,G,10:51:41.75,+18:28:51.0,Leo -NGC3427,G,10:51:26.33,+08:17:55.3,Leo -NGC3428,G,10:51:29.51,+09:16:46.2,Leo -NGC3429,Dup,10:51:29.51,+09:16:46.2,Leo -NGC3430,G,10:52:11.40,+32:57:01.6,LMi -NGC3431,G,10:51:15.04,-17:00:28.9,Crt -NGC3432,G,10:52:31.13,+36:37:07.6,LMi -NGC3433,G,10:52:03.87,+10:08:53.9,Leo -NGC3434,G,10:51:58.03,+03:47:31.4,Leo -NGC3435,G,10:54:48.33,+61:17:23.5,UMa -NGC3436,G,10:52:27.49,+08:05:38.5,Leo -NGC3437,G,10:52:35.75,+22:56:02.9,Leo -NGC3438,G,10:52:25.98,+10:32:50.1,Leo -NGC3439,G,10:52:25.72,+08:33:27.3,Leo -NGC3440,G,10:53:49.50,+57:07:07.5,UMa -NGC3441,G,10:52:31.12,+07:13:29.6,Leo -NGC3442,G,10:53:08.11,+33:54:37.3,LMi -NGC3443,G,10:53:00.12,+17:34:25.1,Leo -NGC3444,G,10:52:59.38,+10:12:38.1,Leo -NGC3445,G,10:54:35.49,+56:59:26.5,UMa -NGC3446,OCl,10:52:06.93,-45:08:21.3,Vel -NGC3447,GPair,10:52:26.80,+16:46:44.0,Leo -NGC3447A,G,10:53:23.98,+16:46:20.8,Leo -NGC3447B,G,10:53:29.65,+16:47:09.6,Leo -NGC3448,G,10:54:39.20,+54:18:17.5,UMa -NGC3449,G,10:52:53.66,-32:55:39.4,Ant -NGC3450,G,10:48:03.62,-20:50:57.1,Hya -NGC3451,G,10:54:20.89,+27:14:23.1,LMi -NGC3452,G,10:54:14.08,-11:24:18.2,Crt -NGC3453,G,10:53:40.50,-21:47:33.9,Hya -NGC3454,G,10:54:29.53,+17:20:38.5,Leo -NGC3455,G,10:54:31.09,+17:17:05.1,Leo -NGC3456,G,10:54:03.31,-16:01:39.8,Crt -NGC3457,G,10:54:48.63,+17:37:16.5,Leo -NGC3458,G,10:56:01.48,+57:07:01.1,UMa -NGC3459,G,10:54:44.28,-17:02:31.2,Crt -NGC3460,Dup,10:54:48.63,+17:37:16.5,Leo -NGC3461,G,10:54:55.29,+17:42:29.3,Leo -NGC3462,G,10:55:21.06,+07:41:48.3,Leo -NGC3463,G,10:55:13.36,-26:08:26.7,Hya -NGC3464,G,10:54:40.01,-21:03:59.9,Hya -NGC3465,G,10:59:31.27,+75:11:28.6,Dra -NGC3466,G,10:56:15.48,+09:45:16.0,Leo -NGC3467,G,10:56:44.08,+09:45:32.1,Leo -NGC3468,G,10:57:31.17,+40:56:46.1,UMa -NGC3469,G,10:56:57.69,-14:18:02.9,Crt -NGC3470,G,10:58:44.90,+59:30:38.5,UMa -NGC3471,G,10:59:09.01,+61:31:50.5,UMa -NGC3472,Other,10:57:22.24,-19:38:15.7,Crt -NGC3473,G,10:58:05.17,+17:07:27.9,Leo -NGC3474,G,10:58:08.76,+17:05:44.5,Leo -NGC3475,G,10:58:25.22,+24:13:34.8,Leo -NGC3476,G,10:58:07.60,+09:16:34.1,Leo -NGC3477,G,10:58:12.58,+09:13:04.1,Leo -NGC3478,G,10:59:27.36,+46:07:20.6,UMa -NGC3479,G,10:58:55.48,-14:57:41.0,Crt -NGC3480,Dup,10:58:07.60,+09:16:34.1,Leo -NGC3481,G,10:59:26.17,-07:32:37.1,Crt -NGC3482,G,10:58:34.27,-46:35:02.0,Vel -NGC3483,G,10:59:00.18,-28:28:37.1,Hya -NGC3484,Other,10:57:24.05,-19:37:59.7,Crt -NGC3485,G,11:00:02.38,+14:50:29.7,Leo -NGC3486,G,11:00:23.87,+28:58:30.5,LMi -NGC3487,G,11:00:46.55,+17:35:15.4,Leo -NGC3488,G,11:01:23.61,+57:40:39.6,UMa -NGC3489,G,11:00:18.57,+13:54:04.4,Leo -NGC3490,G,10:59:54.41,+09:21:42.8,Leo -NGC3491,G,11:00:35.40,+12:09:41.6,Leo -NGC3492,GPair,11:00:57.00,+10:30:18.0,Leo -NGC3492 NED01,G,11:00:56.75,+10:30:15.2,Leo -NGC3492 NED02,G,11:00:57.42,+10:30:19.7,Leo -NGC3493,G,11:01:27.83,+27:43:10.5,LMi -NGC3494,**,11:01:10.91,+03:46:27.5,Leo -NGC3495,G,11:01:16.23,+03:37:40.6,Leo -NGC3496,OCl,10:59:33.81,-60:20:12.7,Car -NGC3497,G,11:07:18.07,-19:28:17.6,Crt -NGC3498,Other,11:01:42.19,+14:21:02.6,Leo -NGC3499,G,11:03:11.03,+56:13:18.2,UMa -NGC3500,G,11:01:51.47,+75:12:04.9,Dra -NGC3501,G,11:02:47.27,+17:59:21.6,Leo -NGC3502,Dup,10:58:55.48,-14:57:41.0,Crt -NGC3503,RfN,11:01:17.24,-59:50:44.7,Car -NGC3504,G,11:03:11.21,+27:58:21.0,LMi -NGC3505,Dup,11:02:59.67,-16:17:22.0,Crt -NGC3506,G,11:03:12.97,+11:04:36.0,Leo -NGC3507,G,11:03:25.36,+18:08:07.6,Leo -NGC3508,G,11:02:59.67,-16:17:22.0,Crt -NGC3509,G,11:04:23.55,+04:49:43.0,Leo -NGC3510,G,11:03:43.37,+28:53:13.8,LMi -NGC3511,G,11:03:23.77,-23:05:12.4,Crt -NGC3512,G,11:04:02.94,+28:02:12.5,LMi -NGC3513,G,11:03:46.08,-23:14:43.8,Crt -NGC3514,G,11:03:59.90,-18:46:50.1,Crt -NGC3515,G,11:04:37.24,+28:13:40.7,LMi -NGC3516,G,11:06:47.49,+72:34:06.9,UMa -NGC3517,G,11:05:36.81,+56:31:29.7,UMa -NGC3518,Dup,10:04:02.11,-06:28:29.2,Sex -NGC3519,OCl,11:04:02.77,-61:22:05.7,Car -NGC3520,G,11:07:09.21,-18:01:25.3,Crt -NGC3521,G,11:05:48.58,-00:02:09.1,Leo -NGC3522,G,11:06:40.46,+20:05:08.0,Leo -NGC3523,G,11:03:06.32,+75:06:56.7,Dra -NGC3524,G,11:06:32.10,+11:23:07.5,Leo -NGC3525,Dup,11:07:18.07,-19:28:17.6,Crt -NGC3526,G,11:06:56.63,+07:10:26.1,Leo -NGC3527,G,11:07:18.19,+28:31:40.0,UMa -NGC3528,Dup,11:07:18.07,-19:28:17.6,Crt -NGC3529,G,11:07:19.12,-19:33:20.3,Crt -NGC3530,G,11:08:40.38,+57:13:48.7,UMa -NGC3531,Dup,11:06:56.63,+07:10:26.1,Leo -NGC3532,OCl,11:05:47.82,-58:46:13.8,Car -NGC3533,G,11:07:07.55,-37:10:21.5,Cen -NGC3534,G,11:08:55.66,+26:36:37.8,Leo -NGC3534B,G,11:08:57.17,+26:35:46.1,Leo -NGC3535,G,11:08:33.92,+04:49:54.8,Leo -NGC3536,G,11:08:51.20,+28:28:32.5,UMa -NGC3537,GPair,11:08:26.70,-10:15:28.0,Crt -NGC3537 NED01,G,11:08:26.51,-10:15:21.7,Crt -NGC3537 NED02,G,11:08:26.93,-10:15:31.8,Crt -NGC3538,**,11:11:34.34,+75:34:11.0,Dra -NGC3539,G,11:09:08.84,+28:40:21.1,UMa -NGC3540,G,11:09:16.08,+36:01:15.9,UMa -NGC3541,G,11:08:32.21,-10:29:30.6,Crt -NGC3542,G,11:09:55.47,+36:56:47.4,UMa -NGC3543,G,11:10:56.45,+61:20:49.3,UMa -NGC3544,G,11:11:30.47,-18:17:22.2,Crt -NGC3545,GPair,11:10:12.70,+36:57:56.0,UMa -NGC3545A,G,11:10:12.25,+36:57:53.2,UMa -NGC3545B,G,11:10:13.25,+36:57:59.6,UMa -NGC3546,G,11:09:46.79,-13:22:51.1,Crt -NGC3547,G,11:09:55.94,+10:43:15.0,Leo -NGC3548,Dup,11:09:16.08,+36:01:15.9,UMa -NGC3549,G,11:10:56.87,+53:23:16.0,UMa -NGC3550,GPair,11:10:38.70,+28:46:06.0,UMa -NGC3550 NED01,G,11:10:38.60,+28:46:04.0,UMa -NGC3550 NED02,G,11:10:38.40,+28:46:03.4,UMa -NGC3551,G,11:09:44.44,+21:45:32.1,Leo -NGC3552,G,11:10:42.85,+28:41:35.4,UMa -NGC3553,G,11:10:40.48,+28:41:05.1,UMa -NGC3554,G,11:10:47.82,+28:39:37.0,UMa -NGC3555,G,11:09:50.33,+21:48:36.7,Leo -NGC3556,G,11:11:30.97,+55:40:26.8,UMa -NGC3557,G,11:09:57.64,-37:32:21.0,Cen -NGC3557B,G,11:09:32.13,-37:20:58.7,Cen -NGC3558,G,11:10:55.87,+28:32:37.7,UMa -NGC3559,G,11:10:45.21,+12:00:58.1,Leo -NGC3560,Dup,11:10:45.21,+12:00:58.1,Leo -NGC3561,G,11:11:13.20,+28:41:47.3,UMa -NGC3562,G,11:12:58.68,+72:52:45.5,Dra -NGC3563,GPair,11:11:24.50,+26:57:45.0,Leo -NGC3563A,G,11:11:23.73,+26:57:42.7,Leo -NGC3563B,G,11:11:25.20,+26:57:48.9,Leo -NGC3564,G,11:10:36.38,-37:32:51.3,Cen -NGC3565,G,11:07:47.65,-20:01:17.6,Crt -NGC3566,Dup,11:07:47.65,-20:01:17.6,Crt -NGC3567,G,11:11:18.68,+05:50:10.7,Leo -NGC3568,G,11:10:48.57,-37:26:52.3,Cen -NGC3569,G,11:12:08.09,+35:27:07.7,UMa -NGC3570,G,11:12:03.35,+27:35:23.2,Leo -NGC3571,Dup,11:11:30.47,-18:17:22.2,Crt -NGC3572,OCl,11:10:19.20,-60:14:54.1,Car -NGC3573,G,11:11:18.57,-36:52:31.9,Cen -NGC3574,G,11:12:12.13,+27:37:29.4,Leo -NGC3575,Dup,10:13:31.59,+22:44:15.2,Leo -NGC3576,HII,11:11:31.67,-61:21:46.8,Car -NGC3577,G,11:13:44.90,+48:16:21.8,UMa -NGC3578,Other,11:12:52.79,-15:57:26.1,Crt -NGC3579,Neb,11:11:59.60,-61:14:35.6,Car -NGC3580,G,11:13:15.93,+03:39:26.4,Leo -NGC3581,HII,11:12:01.96,-61:18:06.7,Car -NGC3582,Neb,11:12:11.97,-61:16:24.8,Car -NGC3583,G,11:14:10.89,+48:19:06.7,UMa -NGC3584,Neb,11:12:19.79,-61:13:43.0,Car -NGC3585,G,11:13:17.09,-26:45:17.4,Hya -NGC3586,Neb,11:12:29.94,-61:21:08.1,Car -NGC3587,PN,11:14:47.71,+55:01:08.5,UMa -NGC3588,GPair,11:14:02.70,+20:23:19.0,Leo -NGC3589,G,11:15:13.33,+60:41:59.7,UMa -NGC3590,OCl,11:12:58.97,-60:47:20.5,Car -NGC3591,G,11:14:03.31,-14:05:14.4,Crt -NGC3592,G,11:14:27.37,+17:15:36.0,Leo -NGC3593,G,11:14:37.00,+12:49:03.6,Leo -NGC3594,G,11:16:13.99,+55:42:15.6,UMa -NGC3595,G,11:15:25.55,+47:26:49.3,UMa -NGC3596,G,11:15:06.21,+14:47:13.3,Leo -NGC3597,G,11:14:41.97,-23:43:39.7,Crt -NGC3598,G,11:15:11.67,+17:15:45.7,Leo -NGC3599,G,11:15:26.96,+18:06:37.4,Leo -NGC3600,G,11:15:52.01,+41:35:27.7,UMa -NGC3601,G,11:15:33.30,+05:06:55.5,Leo -NGC3602,G,11:15:48.32,+17:24:58.0,Leo -NGC3603,Cl+N,11:15:06.59,-61:15:40.4,Car -NGC3604,G,11:17:30.17,+04:33:20.1,Leo -NGC3605,G,11:16:46.59,+18:01:01.8,Leo -NGC3606,G,11:16:15.63,-33:49:38.8,Hya -NGC3607,G,11:16:54.64,+18:03:06.3,Leo -NGC3608,G,11:16:58.95,+18:08:55.3,Leo -NGC3609,G,11:17:50.62,+26:37:32.9,Leo -NGC3610,G,11:18:25.27,+58:47:10.6,UMa -NGC3611,Dup,11:17:30.17,+04:33:20.1,Leo -NGC3612,G,11:18:14.72,+26:37:14.0,Leo -NGC3613,G,11:18:36.11,+58:00:00.0,UMa -NGC3614,G,11:18:21.32,+45:44:53.6,UMa -NGC3614A,G,11:18:11.87,+45:43:00.8,UMa -NGC3615,G,11:18:06.65,+23:23:50.4,Leo -NGC3616,*,11:18:08.87,+14:45:54.8,Leo -NGC3617,G,11:17:50.86,-26:08:03.9,Hya -NGC3618,G,11:18:32.54,+23:28:08.7,Leo -NGC3619,G,11:19:21.56,+57:45:28.2,UMa -NGC3620,G,11:16:04.68,-76:12:58.7,Cha -NGC3621,G,11:18:16.51,-32:48:50.6,Hya -NGC3622,G,11:20:12.37,+67:14:29.6,UMa -NGC3623,G,11:18:55.92,+13:05:32.5,Leo -NGC3624,G,11:18:50.98,+07:31:17.0,Leo -NGC3625,G,11:20:31.34,+57:46:53.1,UMa -NGC3626,G,11:20:03.81,+18:21:24.6,Leo -NGC3627,G,11:20:14.96,+12:59:29.5,Leo -NGC3628,G,11:20:16.97,+13:35:22.9,Leo -NGC3629,G,11:20:31.81,+26:57:48.1,Leo -NGC3630,G,11:20:16.98,+02:57:51.8,Leo -NGC3631,G,11:21:02.87,+53:10:10.4,UMa -NGC3632,Dup,11:20:03.81,+18:21:24.6,Leo -NGC3633,G,11:20:26.22,+03:35:08.2,Leo -NGC3634,G,11:20:30.31,-09:00:49.5,Crt -NGC3635,G,11:20:31.39,-09:00:49.0,Crt -NGC3636,G,11:20:25.04,-10:16:54.4,Crt -NGC3637,G,11:20:39.57,-10:15:26.5,Crt -NGC3638,G,11:20:10.03,-08:06:20.5,Crt -NGC3639,G,11:21:35.68,+18:27:30.9,Leo -NGC3640,G,11:21:06.85,+03:14:05.4,Leo -NGC3641,G,11:21:08.81,+03:11:40.5,Leo -NGC3642,G,11:22:17.89,+59:04:28.3,UMa -NGC3643,G,11:21:24.99,+03:00:50.1,Leo -NGC3644,G,11:21:32.87,+02:48:37.6,Leo -NGC3645,Dup,11:20:16.98,+02:57:51.8,Leo -NGC3646,G,11:21:43.08,+20:10:10.4,Leo -NGC3647,G,11:21:38.58,+02:53:30.2,Leo -NGC3648,G,11:22:31.51,+39:52:36.8,UMa -NGC3649,G,11:22:14.74,+20:12:30.7,Leo -NGC3650,G,11:22:35.39,+20:42:14.1,Leo -NGC3651,GPair,11:22:26.30,+24:17:50.0,Leo -NGC3651 NED01,G,11:22:26.32,+24:17:56.7,Leo -NGC3651 NED02,G,11:22:26.38,+24:17:43.6,Leo -NGC3652,G,11:22:39.03,+37:45:54.4,UMa -NGC3653,G,11:22:30.07,+24:16:45.4,Leo -NGC3654,G,11:24:10.74,+69:24:47.1,UMa -NGC3655,G,11:22:54.62,+16:35:24.1,Leo -NGC3656,G,11:23:38.65,+53:50:31.6,UMa -NGC3657,G,11:23:55.58,+52:55:15.6,UMa -NGC3658,G,11:23:58.26,+38:33:44.4,UMa -NGC3659,G,11:23:45.54,+17:49:07.2,Leo -NGC3660,G,11:23:32.28,-08:39:30.8,Crt -NGC3661,G,11:23:38.43,-13:49:52.0,Crt -NGC3662,G,11:23:46.46,-01:06:17.5,Leo -NGC3663,G,11:23:59.91,-12:17:47.2,Crt -NGC3664,G,11:24:24.25,+03:19:30.0,Leo -NGC3664A,G,11:24:25.10,+03:13:21.0,Leo -NGC3665,G,11:24:43.67,+38:45:46.3,UMa -NGC3666,G,11:24:26.07,+11:20:32.0,Leo -NGC3667,G,11:24:17.02,-13:51:26.3,Crt -NGC3667A,G,11:24:21.49,-13:51:20.9,Crt -NGC3668,G,11:25:30.44,+63:26:46.8,UMa -NGC3669,G,11:25:26.76,+57:43:16.5,UMa -NGC3670,G,11:24:49.66,+23:56:42.8,Leo -NGC3671,G,11:25:52.55,+60:28:46.5,UMa -NGC3672,G,11:25:02.47,-09:47:43.4,Crt -NGC3673,G,11:25:12.85,-26:44:12.2,Hya -NGC3674,G,11:26:26.61,+57:02:54.2,UMa -NGC3675,G,11:26:08.58,+43:35:09.3,UMa -NGC3676,G,11:25:37.50,-11:08:22.8,Crt -NGC3677,G,11:26:17.76,+46:58:26.8,UMa -NGC3678,G,11:26:15.76,+27:52:01.7,Leo -NGC3679,G,11:21:48.06,-05:45:27.7,Leo -NGC3680,OCl,11:25:37.08,-43:15:00.4,Cen -NGC3681,G,11:26:29.80,+16:51:47.5,Leo -NGC3682,G,11:27:41.20,+66:35:23.4,Dra -NGC3683,G,11:27:31.85,+56:52:37.4,UMa -NGC3683A,G,11:29:11.76,+57:07:56.4,UMa -NGC3684,G,11:27:11.20,+17:01:48.6,Leo -NGC3685,G,11:28:16.22,+04:19:38.2,Leo -NGC3686,G,11:27:43.97,+17:13:27.1,Leo -NGC3687,G,11:28:00.61,+29:30:39.8,UMa -NGC3688,G,11:27:44.45,-09:09:56.2,Crt -NGC3689,G,11:28:11.03,+25:39:40.2,Leo -NGC3690,GPair,11:28:32.30,+58:33:43.0,UMa -NGC3690A,G,11:28:31.02,+58:33:40.7,UMa -NGC3690B,G,11:28:33.63,+58:33:46.6,UMa -NGC3691,G,11:28:09.41,+16:55:13.7,Leo -NGC3692,G,11:28:24.01,+09:24:27.5,Leo -NGC3693,G,11:28:11.57,-13:11:41.5,Crt -NGC3694,G,11:28:54.13,+35:24:50.4,UMa -NGC3695,G,11:29:17.09,+35:34:32.6,UMa -NGC3696,G,11:28:43.89,-11:16:58.2,Crt -NGC3697,G,11:28:50.38,+20:47:42.1,Leo -NGC3698,Dup,11:29:17.09,+35:34:32.6,UMa -NGC3699,PN,11:27:57.15,-59:57:29.0,Cen -NGC3700,G,11:29:38.61,+35:30:52.7,UMa -NGC3701,G,11:29:28.92,+24:05:36.4,Leo -NGC3702,G,11:30:13.48,-08:51:46.5,Crt -NGC3703,G,11:29:09.36,-08:26:47.2,Crt -NGC3704,G,11:30:04.65,-11:32:46.8,Crt -NGC3705,G,11:30:07.46,+09:16:35.9,Leo -NGC3705A,G,11:30:29.72,+09:23:16.6,Leo -NGC3705B,G,11:29:43.70,+09:12:20.9,Leo -NGC3706,G,11:29:44.43,-36:23:28.7,Cen -NGC3707,G,11:30:11.56,-11:32:36.6,Crt -NGC3708,Other,11:30:39.28,-03:13:21.3,Leo -NGC3709,Other,11:30:39.27,-03:15:21.3,Leo -NGC3710,G,11:31:06.94,+22:46:05.0,Leo -NGC3711,G,11:29:25.52,-11:04:44.7,Crt -NGC3712,G,11:31:09.15,+28:34:04.6,UMa -NGC3713,G,11:31:42.02,+28:09:12.9,Leo -NGC3714,G,11:31:53.61,+28:21:30.5,UMa -NGC3715,G,11:31:32.34,-14:13:52.9,Crt -NGC3716,G,11:31:41.13,+03:29:16.8,Leo -NGC3717,G,11:31:31.99,-30:18:27.9,Hya -NGC3718,G,11:32:34.85,+53:04:04.5,UMa -NGC3719,G,11:32:13.46,+00:49:09.4,Leo -NGC3720,G,11:32:21.60,+00:48:14.4,Leo -NGC3721,G,11:34:07.82,-09:28:01.7,Crt -NGC3722,G,11:34:23.31,-09:40:48.2,Crt -NGC3723,G,11:32:30.55,-09:58:10.3,Crt -NGC3724,G,11:34:28.68,-09:39:36.7,Crt -NGC3725,G,11:33:40.53,+61:53:16.8,UMa -NGC3726,G,11:33:21.12,+47:01:45.1,UMa -NGC3727,G,11:33:40.93,-13:52:44.0,Crt -NGC3728,G,11:33:15.78,+24:26:48.8,Leo -NGC3729,G,11:33:49.32,+53:07:32.0,UMa -NGC3730,G,11:34:16.88,-09:34:34.0,Crt -NGC3731,G,11:34:11.68,+12:30:44.3,Leo -NGC3732,G,11:34:13.94,-09:50:44.4,Crt -NGC3733,G,11:35:01.60,+54:51:01.8,UMa -NGC3734,G,11:34:40.68,-14:04:54.6,Crt -NGC3735,G,11:35:57.30,+70:32:08.1,Dra -NGC3736,G,11:35:41.73,+73:27:06.6,Dra -NGC3737,G,11:35:36.38,+54:56:54.8,UMa -NGC3738,G,11:35:48.79,+54:31:26.0,UMa -NGC3739,G,11:35:37.60,+25:05:19.1,Leo -NGC3740,G,11:36:12.28,+59:58:35.4,UMa -NGC3741,G,11:36:06.18,+45:17:01.1,UMa -NGC3742,G,11:35:32.51,-37:57:23.0,Cen -NGC3743,G,11:35:57.36,+21:43:21.4,Leo -NGC3744,G,11:35:57.87,+23:00:41.6,Leo -NGC3745,G,11:37:44.43,+22:01:16.6,Leo -NGC3746,G,11:37:43.62,+22:00:35.4,Leo -NGC3747,G,11:32:31.06,+74:22:42.2,Dra -NGC3748,G,11:37:49.06,+22:01:34.1,Leo -NGC3749,G,11:35:53.21,-37:59:50.5,Cen -NGC3750,G,11:37:51.64,+21:58:27.3,Leo -NGC3751,G,11:37:53.86,+21:56:11.3,Leo -NGC3752,G,11:32:32.21,+74:37:39.1,Dra -NGC3753,G,11:37:53.90,+21:58:53.0,Leo -NGC3754,G,11:37:54.92,+21:59:07.8,Leo -NGC3755,G,11:36:33.37,+36:24:37.2,UMa -NGC3756,G,11:36:48.02,+54:17:36.8,UMa -NGC3757,G,11:37:02.86,+58:24:55.9,UMa -NGC3758,G,11:36:29.10,+21:35:46.0,Leo -NGC3759,G,11:36:54.09,+54:49:23.9,UMa -NGC3759A,G,11:36:58.03,+55:09:43.4,UMa -NGC3760,Dup,10:36:56.04,+21:52:55.7,Leo -NGC3761,G,11:36:44.12,+22:59:31.3,Leo -NGC3762,G,11:37:23.83,+61:45:33.7,UMa -NGC3763,G,11:36:30.19,-09:50:48.1,Crt -NGC3764,GPair,11:36:54.10,+17:53:22.0,Leo -NGC3764 NED01,G,11:36:54.62,+17:53:19.0,Leo -NGC3764 NED02,G,11:36:53.66,+17:53:25.6,Leo -NGC3765,G,11:37:04.18,+24:05:46.5,Leo -NGC3766,OCl,11:36:14.39,-61:36:18.6,Cen -NGC3767,G,11:37:15.56,+16:52:37.8,Leo -NGC3768,G,11:37:14.48,+17:50:23.6,Leo -NGC3769,G,11:37:44.11,+47:53:35.1,UMa -NGC3769A,G,11:37:51.36,+47:52:52.8,UMa -NGC3770,G,11:37:58.74,+59:37:00.9,UMa -NGC3771,G,11:39:06.00,-09:20:53.5,Crt -NGC3772,G,11:37:48.48,+22:41:28.6,Leo -NGC3773,G,11:38:12.88,+12:06:43.4,Leo -NGC3774,G,11:38:30.26,-08:58:34.1,Crt -NGC3775,G,11:38:26.70,-10:38:19.6,Crt -NGC3776,G,11:38:17.98,-03:21:15.8,Vir -NGC3777,G,11:36:06.85,-12:34:08.7,Crt -NGC3778,G,11:38:21.41,-50:42:55.3,Cen -NGC3779,G,11:38:51.05,-10:35:01.5,Crt -NGC3780,G,11:39:22.36,+56:16:14.4,UMa -NGC3781,G,11:39:03.76,+26:21:42.3,Leo -NGC3782,G,11:39:20.76,+46:30:49.8,UMa -NGC3783,G,11:39:01.76,-37:44:19.2,Cen -NGC3784,G,11:39:29.80,+26:18:32.9,Leo -NGC3785,G,11:39:32.89,+26:18:08.2,Leo -NGC3786,G,11:39:42.55,+31:54:33.4,UMa -NGC3787,G,11:39:37.95,+20:27:16.9,Leo -NGC3788,G,11:39:44.65,+31:55:52.3,UMa -NGC3789,G,11:38:09.06,-09:36:25.7,Crt -NGC3790,G,11:39:47.26,+17:42:44.1,Leo -NGC3791,G,11:39:41.70,-09:22:02.1,Crt -NGC3792,**,11:39:38.53,+05:05:58.2,Vir -NGC3793,*,11:40:01.99,+31:52:39.8,UMa -NGC3794,G,11:40:53.42,+56:12:07.3,UMa -NGC3795,G,11:40:06.67,+58:36:47.1,UMa -NGC3795A,G,11:39:21.31,+58:16:07.3,UMa -NGC3796,G,11:40:31.12,+60:17:56.1,UMa -NGC3797,*,11:40:13.31,+31:54:23.6,UMa -NGC3798,G,11:40:13.94,+24:41:49.4,Leo -NGC3799,G,11:40:09.38,+15:19:38.3,Leo -NGC3800,G,11:40:13.51,+15:20:32.5,Leo -NGC3801,G,11:40:16.94,+17:43:41.0,Leo -NGC3802,G,11:40:18.78,+17:45:55.2,Leo -NGC3803,G,11:40:17.25,+17:48:04.5,Leo -NGC3804,Dup,11:40:53.42,+56:12:07.3,UMa -NGC3805,G,11:40:41.68,+20:20:34.6,Leo -NGC3806,G,11:40:46.63,+17:47:46.6,Leo -NGC3807,*,11:41:54.68,+17:49:08.1,Leo -NGC3808,GPair,11:40:44.40,+22:26:16.0,Leo -NGC3808A,G,11:40:44.24,+22:25:45.7,Leo -NGC3808B,G,11:40:44.64,+22:26:49.0,Leo -NGC3809,G,11:41:16.06,+59:53:08.8,UMa -NGC3810,G,11:40:58.76,+11:28:16.1,Leo -NGC3811,G,11:41:16.63,+47:41:26.9,UMa -NGC3812,G,11:41:07.71,+24:49:18.1,Leo -NGC3813,G,11:41:18.66,+36:32:48.5,UMa -NGC3814,G,11:41:27.68,+24:48:19.5,Leo -NGC3815,G,11:41:39.29,+24:48:01.8,Leo -NGC3816,G,11:41:48.04,+20:06:13.1,Leo -NGC3817,G,11:41:52.95,+10:18:15.8,Vir -NGC3818,G,11:41:57.36,-06:09:20.4,Vir -NGC3819,G,11:42:05.86,+10:21:04.1,Leo -NGC3820,G,11:42:04.91,+10:23:03.3,Leo -NGC3821,G,11:42:09.11,+20:18:56.4,Leo -NGC3822,G,11:42:11.11,+10:16:40.0,Vir -NGC3823,G,11:42:15.10,-13:52:01.0,Crt -NGC3824,G,11:42:44.86,+52:46:46.5,UMa -NGC3825,G,11:42:23.73,+10:15:50.9,Vir -NGC3826,G,11:42:32.85,+26:29:20.0,Leo -NGC3827,G,11:42:36.27,+18:50:43.6,Leo -NGC3828,G,11:42:58.42,+16:29:15.4,Leo -NGC3829,G,11:43:27.28,+52:42:40.1,UMa -NGC3830,Dup,11:42:32.85,+26:29:20.0,Leo -NGC3831,G,11:43:18.60,-12:52:42.1,Crt -NGC3832,G,11:43:31.44,+22:43:31.6,Leo -NGC3833,G,11:43:28.98,+10:09:42.7,Vir -NGC3834,G,11:43:37.73,+19:05:25.7,Leo -NGC3835,G,11:44:04.90,+60:07:11.2,UMa -NGC3835A,G,11:47:22.90,+60:18:01.6,UMa -NGC3836,GPair,11:43:29.80,-16:47:44.0,Crt -NGC3836 NED01,G,11:43:29.51,-16:47:39.3,Crt -NGC3836 NED02,G,11:43:29.84,-16:47:47.3,Crt -NGC3837,G,11:43:56.43,+19:53:40.5,Leo -NGC3838,G,11:44:13.76,+57:56:53.6,UMa -NGC3839,G,11:43:54.33,+10:47:04.9,Leo -NGC3840,G,11:43:58.96,+20:04:37.3,Leo -NGC3841,G,11:44:02.15,+19:58:18.8,Leo -NGC3842,G,11:44:02.15,+19:56:59.3,Leo -NGC3843,G,11:43:54.63,+07:55:32.5,Vir -NGC3844,G,11:44:00.79,+20:01:45.6,Leo -NGC3845,G,11:44:05.47,+19:59:46.0,Leo -NGC3846,G,11:44:29.08,+55:39:08.1,UMa -NGC3846A,G,11:44:14.82,+55:02:05.9,UMa -NGC3847,G,11:44:13.97,+33:30:52.3,UMa -NGC3848,Dup,11:42:11.11,+10:16:40.0,Vir -NGC3849,G,11:45:35.25,+03:13:54.6,Vir -NGC3850,G,11:45:35.56,+55:53:12.8,UMa -NGC3851,G,11:44:20.42,+19:58:51.1,Leo -NGC3852,Dup,11:42:23.73,+10:15:50.9,Vir -NGC3853,G,11:44:28.33,+16:33:29.2,Leo -NGC3854,G,11:44:52.05,-09:13:59.8,Crt -NGC3855,G,11:44:25.78,+33:21:18.3,UMa -NGC3856,Dup,11:44:13.97,+33:30:52.3,UMa -NGC3857,G,11:44:50.15,+19:31:58.3,Leo -NGC3858,G,11:45:11.68,-09:18:50.3,Crt -NGC3859,G,11:44:52.24,+19:27:15.1,Leo -NGC3860,G,11:44:49.16,+19:47:42.1,Leo -NGC3861,G,11:45:03.88,+19:58:25.3,Leo -NGC3862,G,11:45:05.01,+19:36:22.7,Leo -NGC3863,G,11:45:05.52,+08:28:10.2,Vir -NGC3864,G,11:45:15.68,+19:23:31.6,Leo -NGC3865,Dup,11:44:52.05,-09:13:59.8,Crt -NGC3866,Dup,11:45:11.68,-09:18:50.3,Crt -NGC3867,G,11:45:29.62,+19:24:00.6,Leo -NGC3868,G,11:45:29.94,+19:26:40.9,Leo -NGC3869,G,11:45:45.56,+10:49:28.6,Leo -NGC3870,G,11:45:56.60,+50:11:59.1,UMa -NGC3871,G,11:46:10.14,+33:06:31.5,UMa -NGC3872,G,11:45:49.06,+13:46:00.1,Leo -NGC3873,G,11:45:46.10,+19:46:26.2,Leo -NGC3874,**,11:45:37.75,+08:34:26.1,Vir -NGC3875,G,11:45:49.45,+19:46:02.7,Leo -NGC3876,G,11:45:26.67,+09:09:38.6,Vir -NGC3877,G,11:46:07.70,+47:29:39.6,UMa -NGC3878,G,11:46:17.78,+33:12:16.1,UMa -NGC3879,G,11:46:49.42,+69:23:01.3,Dra -NGC3880,G,11:46:22.23,+33:09:42.4,UMa -NGC3881,G,11:46:34.41,+33:06:23.1,UMa -NGC3882,G,11:46:06.55,-56:23:17.3,Cen -NGC3883,G,11:46:47.18,+20:40:31.5,Leo -NGC3884,G,11:46:12.18,+20:23:29.9,Leo -NGC3885,G,11:46:46.49,-27:55:19.8,Hya -NGC3886,G,11:47:05.60,+19:50:13.9,Leo -NGC3887,G,11:47:04.57,-16:51:16.6,Crt -NGC3888,G,11:47:34.37,+55:58:02.0,UMa -NGC3889,G,11:47:48.13,+56:01:06.0,UMa -NGC3890,G,11:49:19.85,+74:18:07.9,Dra -NGC3891,G,11:48:03.36,+30:21:33.6,UMa -NGC3892,G,11:48:00.99,-10:57:43.4,Crt -NGC3893,G,11:48:38.19,+48:42:39.0,UMa -NGC3894,G,11:48:50.36,+59:24:56.4,UMa -NGC3895,G,11:49:04.03,+59:25:57.6,UMa -NGC3896,G,11:48:56.39,+48:40:28.8,UMa -NGC3897,G,11:48:59.46,+35:00:57.8,UMa -NGC3898,G,11:49:15.37,+56:05:03.7,UMa -NGC3899,G,11:50:04.45,+26:28:45.3,Leo -NGC3900,G,11:49:09.46,+27:01:19.3,Leo -NGC3901,G,11:42:49.72,+77:22:21.7,Cam -NGC3902,G,11:49:18.76,+26:07:17.8,Leo -NGC3903,G,11:49:03.54,-37:31:01.6,Cen -NGC3904,G,11:49:13.22,-29:16:36.3,Hya -NGC3905,G,11:49:04.91,-09:43:47.4,Crt -NGC3906,G,11:49:40.50,+48:25:33.5,UMa -NGC3907,G,11:49:30.14,-01:05:11.5,Vir -NGC3907B,G,11:49:23.54,-01:05:01.6,Vir -NGC3908,G,11:49:52.68,+12:11:09.3,Leo -NGC3909,OCl,11:50:08.14,-48:14:17.3,Cen -NGC3910,G,11:49:59.31,+21:20:01.1,Leo -NGC3911,G,11:49:22.09,+24:56:18.5,Leo -NGC3912,Dup,11:50:04.45,+26:28:45.3,Leo -NGC3913,G,11:50:38.94,+55:21:13.9,UMa -NGC3914,G,11:50:32.65,+06:34:03.3,Vir -NGC3915,G,11:49:24.53,-05:07:06.4,Vir -NGC3916,G,11:50:51.05,+55:08:37.1,UMa -NGC3917,G,11:50:45.43,+51:49:28.8,UMa -NGC3918,PN,11:50:17.95,-57:10:56.4,Cen -NGC3919,G,11:50:41.53,+20:00:54.7,Leo -NGC3920,G,11:50:05.93,+24:55:12.0,Leo -NGC3921,G,11:51:06.87,+55:04:43.5,UMa -NGC3922,G,11:51:13.43,+50:09:24.8,UMa -NGC3923,G,11:51:01.69,-28:48:21.7,Hya -NGC3924,Dup,11:51:13.43,+50:09:24.8,UMa -NGC3925,G,11:51:20.97,+21:53:20.9,Leo -NGC3926,GPair,11:51:27.40,+22:01:37.0,Leo -NGC3926A,G,11:51:26.56,+22:01:40.7,Leo -NGC3926B,G,11:51:28.23,+22:01:33.5,Leo -NGC3927,Dup,11:31:42.02,+28:09:12.9,Leo -NGC3928,G,11:51:47.62,+48:40:59.3,UMa -NGC3929,G,11:51:42.53,+21:00:09.8,Leo -NGC3930,G,11:51:46.01,+38:00:54.4,UMa -NGC3931,G,11:51:13.45,+52:00:03.1,UMa -NGC3932,*,11:52:10.84,+48:37:13.4,UMa -NGC3933,G,11:52:02.05,+16:48:34.9,Leo -NGC3934,G,11:52:12.56,+16:51:05.2,Leo -NGC3935,G,11:52:24.07,+32:24:13.7,UMa -NGC3936,G,11:52:20.59,-26:54:21.2,Hya -NGC3937,G,11:52:42.62,+20:37:52.7,Leo -NGC3938,G,11:52:49.45,+44:07:14.6,UMa -NGC3939,Dup,11:49:19.85,+74:18:07.9,Dra -NGC3940,G,11:52:46.45,+20:59:21.4,Leo -NGC3941,G,11:52:55.36,+36:59:10.8,UMa -NGC3942,G,11:51:30.14,-11:25:29.0,Crt -NGC3943,G,11:52:56.57,+20:28:44.8,Leo -NGC3944,G,11:53:05.08,+26:12:25.0,Leo -NGC3945,G,11:53:13.73,+60:40:32.0,UMa -NGC3946,G,11:53:20.63,+21:01:17.5,Leo -NGC3947,G,11:53:20.32,+20:45:06.2,Leo -NGC3948,*,11:53:36.65,+20:57:02.8,Leo -NGC3949,G,11:53:41.72,+47:51:31.3,UMa -NGC3950,G,11:53:41.41,+47:53:04.5,UMa -NGC3951,G,11:53:41.25,+23:22:56.0,Leo -NGC3952,G,11:53:40.63,-03:59:47.5,Vir -NGC3953,G,11:53:48.92,+52:19:36.4,UMa -NGC3954,G,11:53:41.68,+20:52:57.0,Leo -NGC3955,G,11:53:57.15,-23:09:51.0,Crt -NGC3956,G,11:54:00.69,-20:34:02.4,Crt -NGC3957,G,11:54:01.51,-19:34:08.0,Crt -NGC3958,G,11:54:33.68,+58:22:01.3,UMa -NGC3959,G,11:54:37.65,-07:45:23.4,Crt -NGC3960,OCl,11:50:33.21,-55:40:11.4,Cen -NGC3961,G,11:54:57.64,+69:19:48.4,Dra -NGC3962,G,11:54:40.10,-13:58:30.1,Crt -NGC3963,G,11:54:58.71,+58:29:37.1,UMa -NGC3964,G,11:54:53.48,+28:15:44.7,Leo -NGC3965,G,11:54:23.11,-10:52:00.7,Crt -NGC3966,G,11:56:44.19,+32:01:18.4,UMa -NGC3967,G,11:55:10.40,-07:50:37.6,Crt -NGC3968,G,11:55:28.70,+11:58:06.1,Leo -NGC3969,G,11:55:09.24,-18:55:38.7,Crt -NGC3970,G,11:55:28.07,-12:03:40.6,Crt -NGC3971,G,11:55:36.39,+29:59:45.3,UMa -NGC3972,G,11:55:45.09,+55:19:14.7,UMa -NGC3973,G,11:55:37.00,+11:59:50.6,Leo -NGC3974,G,11:55:40.14,-12:01:38.8,Crt -NGC3975,G,11:55:53.69,+60:31:45.9,UMa -NGC3976,G,11:55:57.29,+06:44:58.0,Vir -NGC3977,G,11:56:07.20,+55:23:26.8,UMa -NGC3978,G,11:56:10.32,+60:31:21.1,UMa -NGC3979,G,11:56:01.05,-02:43:15.1,Vir -NGC3980,Dup,11:56:07.20,+55:23:26.8,UMa -NGC3981,G,11:56:07.45,-19:53:46.2,Crt -NGC3982,G,11:56:28.13,+55:07:30.9,UMa -NGC3983,G,11:56:23.68,+23:52:04.6,Leo -NGC3984,Dup,11:55:36.39,+29:59:45.3,UMa -NGC3985,G,11:56:42.11,+48:20:02.2,UMa -NGC3986,Dup,11:56:44.19,+32:01:18.4,UMa -NGC3987,G,11:57:20.92,+25:11:43.4,Leo -NGC3988,G,11:57:24.22,+27:52:39.1,Leo -NGC3989,G,11:57:26.69,+25:13:59.1,Leo -NGC3990,G,11:57:35.56,+55:27:31.2,UMa -NGC3991,GPair,11:57:31.08,+32:20:16.0,UMa -NGC3991 NED01,G,11:57:30.38,+32:20:01.8,UMa -NGC3991 NED02,G,11:57:31.73,+32:20:30.2,UMa -NGC3992,G,11:57:35.98,+53:22:28.3,UMa -NGC3993,G,11:57:37.82,+25:14:26.2,Leo -NGC3994,G,11:57:36.87,+32:16:39.4,UMa -NGC3995,G,11:57:44.10,+32:17:38.6,UMa -NGC3996,G,11:57:46.06,+14:17:50.7,Leo -NGC3997,G,11:57:48.23,+25:16:14.3,Leo -NGC3998,G,11:57:56.13,+55:27:12.9,UMa -NGC3999,G,11:57:56.49,+25:04:05.8,Leo -NGC4000,G,11:57:57.02,+25:08:40.1,Leo -NGC4001,G,11:58:06.78,+47:20:05.5,UMa -NGC4002,G,11:57:59.30,+23:12:07.4,Leo -NGC4003,G,11:57:59.04,+23:07:29.6,Leo -NGC4004,G,11:58:05.23,+27:52:43.9,Leo -NGC4004B,G,11:57:51.38,+27:52:07.2,Leo -NGC4005,G,11:58:10.17,+25:07:20.1,Leo -NGC4006,G,11:58:05.78,-02:07:12.3,Vir -NGC4007,Dup,11:58:10.17,+25:07:20.1,Leo -NGC4008,G,11:58:17.04,+28:11:33.0,Leo -NGC4009,*,11:58:15.05,+25:11:23.1,Leo -NGC4010,G,11:58:37.89,+47:15:41.4,UMa -NGC4011,G,11:58:25.43,+25:05:51.5,Leo -NGC4012,G,11:58:27.53,+10:01:17.4,Vir -NGC4013,G,11:58:31.38,+43:56:47.7,UMa -NGC4014,G,11:58:35.81,+16:10:38.3,Com -NGC4015,GPair,11:58:42.90,+25:02:25.0,Com -NGC4015 NED01,G,11:58:42.60,+25:02:11.8,Com -NGC4015 NED02,G,11:58:43.22,+25:02:37.8,Com -NGC4016,G,11:58:29.02,+27:31:43.6,Com -NGC4017,G,11:58:45.67,+27:27:08.8,Com -NGC4018,G,11:58:40.70,+25:18:59.1,Com -NGC4019,G,12:01:10.42,+14:06:15.5,Com -NGC4020,G,11:58:56.67,+30:24:42.8,UMa -NGC4021,G,11:59:02.59,+25:04:59.6,Com -NGC4022,G,11:59:01.01,+25:13:22.1,Com -NGC4023,G,11:59:05.47,+24:59:20.3,Com -NGC4024,G,11:58:31.25,-18:20:48.6,Crv -NGC4025,G,11:59:10.19,+37:47:36.3,UMa -NGC4026,G,11:59:25.19,+50:57:42.1,UMa -NGC4027,G,11:59:30.17,-19:15:54.8,Crv -NGC4027A,G,11:59:29.39,-19:19:55.2,Crv -NGC4028,Dup,11:58:35.81,+16:10:38.3,Com -NGC4029,G,12:00:03.17,+08:10:54.2,Vir -NGC4030,G,12:00:23.63,-01:06:00.3,Vir -NGC4031,G,12:00:31.35,+31:56:51.3,UMa -NGC4032,G,12:00:32.82,+20:04:26.2,Com -NGC4033,G,12:00:34.74,-17:50:33.4,Crv -NGC4034,G,12:01:29.66,+69:19:26.1,Dra -NGC4035,G,12:00:29.34,-15:56:53.1,Crv -NGC4036,G,12:01:26.75,+61:53:44.8,UMa -NGC4037,G,12:01:23.67,+13:24:03.7,Com -NGC4038,G,12:01:53.01,-18:52:03.4,Crv -NGC4039,G,12:01:53.51,-18:53:10.3,Crv -NGC4040,G,12:02:05.44,+17:49:23.6,Com -NGC4041,G,12:02:12.20,+62:08:14.0,UMa -NGC4042,G,12:02:46.78,+20:09:47.7,Com -NGC4043,G,12:02:22.96,+04:19:47.3,Vir -NGC4044,G,12:02:29.51,-00:12:44.7,Vir -NGC4045,G,12:02:42.24,+01:58:36.5,Vir -NGC4045A,G,12:02:42.69,+01:57:08.0,Vir -NGC4046,Dup,12:02:42.24,+01:58:36.5,Vir -NGC4047,G,12:02:50.68,+48:38:10.3,UMa -NGC4048,G,12:02:50.19,+18:00:56.1,Com -NGC4049,G,12:02:54.70,+18:45:09.0,Com -NGC4050,G,12:02:53.95,-16:22:25.0,Crv -NGC4051,G,12:03:09.61,+44:31:52.8,UMa -NGC4052,OCl,12:02:05.19,-63:13:24.5,Cru -NGC4053,G,12:03:11.57,+19:43:43.8,Com -NGC4054,GTrpl,12:03:13.10,+57:53:40.0,UMa -NGC4054 NED01,G,12:03:12.46,+57:53:36.4,UMa -NGC4054 NED02,G,12:03:13.42,+57:53:53.0,UMa -NGC4054 NED03,G,12:03:13.83,+57:53:26.0,UMa -NGC4055,G,12:04:01.48,+20:13:56.4,Com -NGC4056,G,12:03:57.77,+20:18:45.0,Com -NGC4057,G,12:04:06.17,+20:14:06.3,Com -NGC4058,G,12:03:49.06,+03:32:53.6,Vir -NGC4059,Dup,12:04:11.30,+20:24:35.4,Com -NGC4060,G,12:04:01.00,+20:20:14.7,Com -NGC4061,Dup,12:04:01.48,+20:13:56.4,Com -NGC4062,G,12:04:03.83,+31:53:44.9,UMa -NGC4063,G,12:04:05.99,+01:50:49.1,Vir -NGC4064,G,12:04:11.16,+18:26:36.3,Com -NGC4065,Dup,12:04:06.17,+20:14:06.3,Com -NGC4066,G,12:04:09.42,+20:20:52.6,Com -NGC4067,G,12:04:11.54,+10:51:15.8,Vir -NGC4068,G,12:04:00.78,+52:35:17.8,UMa -NGC4069,G,12:04:06.04,+20:19:25.6,Com -NGC4070,G,12:04:11.30,+20:24:35.4,Com -NGC4071,PN,12:04:15.77,-67:18:36.4,Mus -NGC4072,G,12:04:13.84,+20:12:35.0,Com -NGC4073,G,12:04:27.07,+01:53:45.5,Vir -NGC4074,G,12:04:29.68,+20:18:58.4,Com -NGC4075,G,12:04:37.83,+02:04:21.1,Vir -NGC4076,G,12:04:32.52,+20:12:17.8,Com -NGC4077,G,12:04:38.05,+01:47:15.8,Vir -NGC4078,G,12:04:47.64,+10:35:44.1,Vir -NGC4079,G,12:04:49.85,-02:22:56.6,Vir -NGC4080,G,12:04:51.83,+26:59:33.1,Com -NGC4081,G,12:04:33.91,+64:26:12.3,UMa -NGC4082,G,12:05:11.45,+10:40:14.2,Vir -NGC4083,G,12:05:14.05,+10:36:47.6,Vir -NGC4084,G,12:05:15.26,+21:12:52.0,Com -NGC4085,G,12:05:22.71,+50:21:10.6,UMa -NGC4086,G,12:05:29.37,+20:14:48.3,Com -NGC4087,G,12:05:35.33,-26:31:21.5,Hya -NGC4088,G,12:05:34.19,+50:32:20.5,UMa -NGC4089,G,12:05:37.43,+20:33:20.6,Com -NGC4090,G,12:05:27.92,+20:18:31.5,Com -NGC4091,G,12:05:40.09,+20:33:20.6,Com -NGC4092,G,12:05:50.11,+20:28:37.2,Com -NGC4093,G,12:05:51.47,+20:31:19.1,Com -NGC4094,G,12:05:53.96,-14:31:35.1,Crv -NGC4095,G,12:05:54.22,+20:34:21.1,Com -NGC4096,G,12:06:01.13,+47:28:42.4,UMa -NGC4097,G,12:06:02.51,+36:51:49.2,UMa -NGC4098,GPair,12:06:03.90,+20:36:22.0,Com -NGC4098 NED01,G,12:06:03.58,+20:36:29.1,Com -NGC4098 NED02,G,12:06:04.22,+20:36:14.1,Com -NGC4099,Dup,12:06:03.90,+20:36:22.0,Com -NGC4100,G,12:06:08.45,+49:34:57.7,UMa -NGC4101,G,12:06:10.57,+25:33:25.2,Com -NGC4102,G,12:06:22.99,+52:42:39.9,UMa -NGC4103,OCl,12:06:39.57,-61:15:00.3,Cru -NGC4104,G,12:06:38.97,+28:10:27.1,Com -NGC4105,G,12:06:40.77,-29:45:36.8,Hya -NGC4106,G,12:06:44.80,-29:46:05.9,Hya -NGC4107,Dup,12:04:47.64,+10:35:44.1,Vir -NGC4108,G,12:06:44.57,+67:09:47.1,Dra -NGC4108A,G,12:05:49.66,+67:15:07.5,Dra -NGC4108B,G,12:07:11.62,+67:14:06.6,Dra -NGC4109,G,12:06:51.12,+42:59:44.3,CVn -NGC4110,G,12:07:03.45,+18:31:54.2,Com -NGC4111,G,12:07:03.13,+43:03:56.6,CVn -NGC4112,G,12:07:09.47,-40:12:29.4,Cen -NGC4113,G,12:07:08.48,+32:59:45.2,Com -NGC4114,G,12:07:12.29,-14:11:07.7,Crv -NGC4115,*,12:07:09.55,+14:24:23.6,Com -NGC4116,G,12:07:37.15,+02:41:25.8,Vir -NGC4117,G,12:07:46.11,+43:07:34.9,CVn -NGC4118,G,12:07:52.87,+43:06:39.8,CVn -NGC4119,G,12:08:09.62,+10:22:44.0,Vir -NGC4120,G,12:08:31.02,+69:32:41.4,Dra -NGC4121,G,12:07:56.63,+65:06:50.2,Dra -NGC4122,Dup,12:07:08.48,+32:59:45.2,Com -NGC4123,G,12:08:11.11,+02:52:41.8,Vir -NGC4124,Dup,12:08:09.62,+10:22:44.0,Vir -NGC4125,G,12:08:06.02,+65:10:26.9,Dra -NGC4126,G,12:08:37.44,+16:08:34.0,Com -NGC4127,G,12:08:26.36,+76:48:14.5,Cam -NGC4128,G,12:08:32.33,+68:46:03.4,Dra -NGC4129,G,12:08:53.22,-09:02:11.4,Vir -NGC4130,Dup,12:08:53.22,-09:02:11.4,Vir -NGC4131,G,12:08:47.29,+29:18:17.2,Com -NGC4132,G,12:09:01.40,+29:15:00.4,Com -NGC4133,G,12:08:49.96,+74:54:15.5,Dra -NGC4134,G,12:09:10.01,+29:10:36.9,Com -NGC4135,G,12:09:08.81,+44:00:11.5,CVn -NGC4136,G,12:09:17.69,+29:55:39.4,Com -NGC4137,G,12:09:17.51,+44:05:24.6,CVn -NGC4138,G,12:09:29.78,+43:41:07.1,CVn -NGC4139,G,12:04:34.03,+01:48:05.8,Vir -NGC4140,Dup,12:04:38.05,+01:47:15.8,Vir -NGC4141,G,12:09:47.32,+58:50:57.1,UMa -NGC4142,G,12:09:30.19,+53:06:17.8,UMa -NGC4143,G,12:09:36.06,+42:32:03.0,CVn -NGC4144,G,12:09:58.60,+46:27:25.8,UMa -NGC4145,G,12:10:01.52,+39:53:01.9,CVn -NGC4146,G,12:10:18.26,+26:25:50.7,Com -NGC4147,GCl,12:10:06.17,+18:32:31.7,Com -NGC4148,G,12:10:07.97,+35:52:39.4,CVn -NGC4149,G,12:10:32.84,+58:18:14.9,UMa -NGC4150,G,12:10:33.65,+30:24:05.5,Com -NGC4151,G,12:10:32.58,+39:24:20.6,CVn -NGC4152,G,12:10:37.50,+16:01:58.5,Com -NGC4153,Dup,12:10:06.17,+18:32:31.7,Com -NGC4154,Dup,12:10:32.84,+58:18:14.9,UMa -NGC4155,G,12:10:45.69,+19:02:27.0,Com -NGC4156,G,12:10:49.61,+39:28:21.8,CVn -NGC4157,G,12:11:04.37,+50:29:04.8,UMa -NGC4158,G,12:11:10.17,+20:10:32.4,Com -NGC4159,G,12:10:53.62,+76:07:33.1,Dra -NGC4160,**,12:12:01.74,+43:44:08.1,CVn -NGC4161,G,12:11:33.46,+57:44:15.0,UMa -NGC4162,G,12:11:52.47,+24:07:25.2,Com -NGC4163,G,12:12:09.16,+36:10:09.1,CVn -NGC4164,G,12:12:05.46,+13:12:20.3,Vir -NGC4165,G,12:12:11.80,+13:14:47.5,Vir -NGC4166,G,12:12:09.56,+17:45:25.3,Com -NGC4167,Dup,12:12:09.16,+36:10:09.1,CVn -NGC4168,G,12:12:17.27,+13:12:18.7,Vir -NGC4169,G,12:12:18.78,+29:10:45.9,Com -NGC4170,*,12:12:13.24,+29:10:00.5,Com -NGC4171,*,12:12:38.39,+29:13:28.2,Com -NGC4172,G,12:12:14.89,+56:10:39.2,UMa -NGC4173,G,12:12:21.46,+29:12:25.4,Com -NGC4174,G,12:12:26.89,+29:08:57.2,Com -NGC4175,G,12:12:31.05,+29:10:06.4,Com -NGC4176,G,12:12:36.83,-09:09:37.7,Vir -NGC4177,G,12:12:41.26,-14:00:52.4,Crv -NGC4178,G,12:12:46.45,+10:51:57.5,Vir -NGC4179,G,12:12:52.11,+01:17:58.9,Vir -NGC4180,G,12:13:03.05,+07:02:20.2,Vir -NGC4181,G,12:12:48.99,+52:54:11.9,UMa -NGC4182,Dup,12:13:03.05,+07:02:20.2,Vir -NGC4183,G,12:13:16.88,+43:41:54.9,CVn -NGC4184,OCl,12:13:32.59,-62:43:17.1,Cru -NGC4185,G,12:13:22.20,+28:30:39.5,Com -NGC4186,G,12:14:06.53,+14:43:32.9,Com -NGC4187,G,12:13:29.28,+50:44:29.4,CVn -NGC4188,G,12:14:07.36,-12:35:09.6,Crv -NGC4189,G,12:13:47.27,+13:25:29.3,Com -NGC4190,G,12:13:44.77,+36:38:02.5,CVn -NGC4191,G,12:13:50.40,+07:12:03.2,Vir -NGC4192,G,12:13:48.29,+14:54:01.2,Com -NGC4193,G,12:13:53.59,+13:10:22.3,Vir -NGC4194,G,12:14:09.47,+54:31:36.6,UMa -NGC4195,G,12:14:18.08,+59:36:55.6,UMa -NGC4196,G,12:14:29.75,+28:25:24.4,Com -NGC4197,G,12:14:38.55,+05:48:20.6,Vir -NGC4198,G,12:14:22.05,+56:00:41.2,UMa -NGC4199,GPair,12:14:50.20,+59:54:27.0,UMa -NGC4199A,G,12:14:48.63,+59:54:22.3,UMa -NGC4199B,G,12:14:51.70,+59:54:30.2,UMa -NGC4200,G,12:14:44.23,+12:10:50.7,Vir -NGC4201,G,12:14:41.86,-11:34:58.3,Vir -NGC4202,G,12:18:08.55,-01:03:50.8,Vir -NGC4203,G,12:15:05.06,+33:11:50.4,Com -NGC4204,G,12:15:14.44,+20:39:30.9,Com -NGC4205,G,12:14:55.54,+63:46:58.0,Dra -NGC4206,G,12:15:16.81,+13:01:26.3,Vir -NGC4207,G,12:15:30.50,+09:35:05.6,Vir -NGC4208,G,12:15:39.36,+13:54:05.4,Com -NGC4209,*,12:15:25.82,+28:28:06.3,Com -NGC4210,G,12:15:15.83,+65:59:07.2,Dra -NGC4211,G,12:15:35.85,+28:10:39.5,Com -NGC4211A,G,12:15:37.32,+28:10:10.7,Com -NGC4212,Dup,12:15:39.36,+13:54:05.4,Com -NGC4213,G,12:15:37.56,+23:58:54.8,Com -NGC4214,G,12:15:39.17,+36:19:36.8,CVn -NGC4215,G,12:15:54.54,+06:24:04.1,Vir -NGC4216,G,12:15:54.44,+13:08:57.8,Vir -NGC4217,G,12:15:50.90,+47:05:30.4,CVn -NGC4218,G,12:15:46.41,+48:07:51.0,CVn -NGC4219,G,12:16:27.32,-43:19:27.3,Cen -NGC4219A,G,12:17:59.89,-43:32:24.1,Cen -NGC4220,G,12:16:11.71,+47:52:59.7,CVn -NGC4221,G,12:15:59.86,+66:13:50.9,Dra -NGC4222,G,12:16:22.52,+13:18:25.4,Com -NGC4223,G,12:17:25.81,+06:41:24.3,Vir -NGC4224,G,12:16:33.79,+07:27:43.5,Vir -NGC4225,G,12:16:38.37,-12:19:39.6,Crv -NGC4226,G,12:16:26.28,+47:01:30.7,CVn -NGC4227,G,12:16:33.70,+33:31:19.4,CVn -NGC4228,Dup,12:15:39.17,+36:19:36.8,CVn -NGC4229,G,12:16:38.79,+33:33:39.2,CVn -NGC4230,OCl,12:17:09.37,-55:17:10.1,Cen -NGC4231,G,12:16:48.87,+47:27:26.6,CVn -NGC4232,G,12:16:49.03,+47:26:19.7,CVn -NGC4233,G,12:17:07.68,+07:37:27.8,Vir -NGC4234,G,12:17:09.16,+03:40:59.0,Vir -NGC4235,G,12:17:09.88,+07:11:29.7,Vir -NGC4236,G,12:16:42.12,+69:27:45.3,Dra -NGC4237,G,12:17:11.42,+15:19:26.3,Com -NGC4238,G,12:16:55.80,+63:24:35.7,Dra -NGC4239,G,12:17:14.93,+16:31:53.0,Com -NGC4240,G,12:17:24.36,-09:57:05.9,Vir -NGC4241,G,12:17:59.90,+06:39:15.1,Vir -NGC4242,G,12:17:30.18,+45:37:09.5,CVn -NGC4243,Dup,12:17:24.36,-09:57:05.9,Vir -NGC4244,G,12:17:29.66,+37:48:25.6,CVn -NGC4245,G,12:17:36.77,+29:36:28.8,Com -NGC4246,G,12:17:58.12,+07:11:09.3,Vir -NGC4247,G,12:17:58.08,+07:16:26.2,Vir -NGC4248,G,12:17:49.85,+47:24:33.1,CVn -NGC4249,G,12:17:59.39,+05:35:54.9,Vir -NGC4250,G,12:17:26.27,+70:48:09.3,Dra -NGC4251,G,12:18:08.25,+28:10:31.4,Com -NGC4252,G,12:18:30.89,+05:33:34.1,Vir -NGC4253,G,12:18:26.51,+29:48:46.3,Com -NGC4254,G,12:18:49.60,+14:24:59.4,Com -NGC4255,G,12:18:56.15,+04:47:10.1,Vir -NGC4256,G,12:18:43.09,+65:53:53.7,Dra -NGC4257,G,12:19:06.47,+05:43:33.5,Vir -NGC4258,G,12:18:57.50,+47:18:14.3,CVn -NGC4259,G,12:19:22.21,+05:22:35.0,Vir -NGC4260,G,12:19:22.24,+06:05:55.2,Vir -NGC4261,G,12:19:23.22,+05:49:30.8,Vir -NGC4262,G,12:19:30.57,+14:52:39.6,Com -NGC4263,G,12:19:42.20,-12:13:32.0,Crv -NGC4264,G,12:19:35.76,+05:50:48.3,Vir -NGC4265,Dup,12:19:42.20,-12:13:32.0,Crv -NGC4266,G,12:19:42.30,+05:32:17.8,Vir -NGC4267,G,12:19:45.24,+12:47:53.8,Vir -NGC4268,G,12:19:47.22,+05:17:01.6,Vir -NGC4269,G,12:19:49.19,+06:00:53.9,Vir -NGC4270,G,12:19:49.47,+05:27:48.4,Vir -NGC4271,G,12:19:32.60,+56:44:11.7,UMa -NGC4272,G,12:19:47.64,+30:20:20.7,Com -NGC4273,G,12:19:56.08,+05:20:36.0,Vir -NGC4274,G,12:19:50.59,+29:36:52.1,Com -NGC4275,G,12:19:52.58,+27:37:15.6,Com -NGC4276,G,12:20:07.48,+07:41:30.7,Vir -NGC4277,G,12:20:03.72,+05:20:28.9,Vir -NGC4278,G,12:20:06.82,+29:16:50.7,Com -NGC4279,G,12:20:25.00,-11:39:59.8,Vir -NGC4280,Other,12:20:31.90,-11:39:08.8,Vir -NGC4281,G,12:20:21.52,+05:23:11.0,Vir -NGC4282,G,12:20:24.31,+05:34:22.2,Vir -NGC4283,G,12:20:20.85,+29:18:39.4,Com -NGC4284,G,12:20:12.62,+58:05:34.4,UMa -NGC4285,G,12:20:39.80,-11:38:30.9,Vir -NGC4286,G,12:20:42.09,+29:20:45.2,Com -NGC4287,G,12:20:48.50,+05:38:23.6,Vir -NGC4288,G,12:20:38.11,+46:17:30.0,CVn -NGC4288A,G,12:20:40.57,+46:15:19.6,CVn -NGC4289,G,12:21:02.25,+03:43:19.7,Vir -NGC4290,G,12:20:47.53,+58:05:33.0,UMa -NGC4291,G,12:20:18.20,+75:22:15.0,Dra -NGC4292,G,12:21:16.46,+04:35:44.5,Vir -NGC4293,G,12:21:12.89,+18:22:56.6,Com -NGC4294,G,12:21:17.83,+11:30:37.6,Vir -NGC4295,G,12:21:09.78,+28:09:54.4,Com -NGC4296,G,12:21:28.39,+06:39:12.8,Vir -NGC4297,G,12:21:27.41,+06:40:15.7,Vir -NGC4298,G,12:21:32.76,+14:36:22.2,Com -NGC4299,G,12:21:40.55,+11:29:59.7,Vir -NGC4300,G,12:21:41.47,+05:23:05.3,Vir -NGC4301,G,12:22:27.21,+04:33:58.6,Vir -NGC4302,G,12:21:42.48,+14:35:53.9,Com -NGC4303,G,12:21:54.90,+04:28:25.1,Vir -NGC4303A,Dup,12:22:27.21,+04:33:58.6,Vir -NGC4304,G,12:22:12.72,-33:29:04.2,Hya -NGC4305,G,12:22:03.60,+12:44:27.3,Vir -NGC4306,G,12:22:04.11,+12:47:14.9,Vir -NGC4307,G,12:22:05.68,+09:02:37.1,Vir -NGC4307A,G,12:22:07.32,+08:59:26.0,Vir -NGC4308,G,12:21:56.85,+30:04:27.7,Com -NGC4309,G,12:22:12.38,+07:08:39.5,Vir -NGC4310,G,12:22:26.30,+29:12:32.5,Com -NGC4311,**,12:22:26.85,+29:08:45.4,Com -NGC4312,G,12:22:31.36,+15:32:16.5,Com -NGC4313,G,12:22:38.54,+11:48:03.4,Vir -NGC4314,G,12:22:31.82,+29:53:45.2,Com -NGC4315,*,12:22:45.14,+09:18:19.4,Vir -NGC4316,G,12:22:42.24,+09:19:56.9,Vir -NGC4317,Other,12:22:41.98,+31:02:16.3,Com -NGC4318,G,12:22:43.29,+08:11:53.8,Vir -NGC4319,G,12:21:43.87,+75:19:21.3,Dra -NGC4320,G,12:22:57.72,+10:32:54.0,Vir -NGC4321,G,12:22:54.83,+15:49:18.5,Com -NGC4322,*,12:22:41.89,+15:54:11.8,Com -NGC4323,G,12:23:01.73,+15:54:20.1,Com -NGC4324,G,12:23:06.18,+05:15:01.2,Vir -NGC4325,G,12:23:06.68,+10:37:16.3,Vir -NGC4326,G,12:23:11.63,+06:04:19.7,Vir -NGC4327,*,12:23:07.51,+15:44:11.4,Com -NGC4328,G,12:23:20.03,+15:49:13.4,Com -NGC4329,G,12:23:20.71,-12:33:31.0,Crv -NGC4330,G,12:23:17.25,+11:22:04.7,Vir -NGC4331,G,12:22:35.96,+76:10:20.9,Dra -NGC4332,G,12:22:46.76,+65:50:37.6,Dra -NGC4333,G,12:23:22.27,+06:02:26.6,Vir -NGC4334,G,12:23:23.88,+07:28:23.5,Vir -NGC4335,G,12:23:01.88,+58:26:40.4,UMa -NGC4336,G,12:23:29.83,+19:25:36.9,Com -NGC4337,OCl,12:24:03.31,-58:07:25.6,Cru -NGC4338,Dup,12:22:26.30,+29:12:32.5,Com -NGC4339,G,12:23:34.95,+06:04:54.3,Vir -NGC4340,G,12:23:35.29,+16:43:20.5,Com -NGC4341,G,12:23:53.56,+07:06:25.6,Vir -NGC4342,G,12:23:39.00,+07:03:14.4,Vir -NGC4343,G,12:23:38.70,+06:57:14.7,Vir -NGC4344,G,12:23:37.47,+17:32:27.1,Com -NGC4345,Dup,12:21:43.87,+75:19:21.3,Dra -NGC4346,G,12:23:27.94,+46:59:37.8,CVn -NGC4347,*,12:23:52.44,-03:14:25.5,Vir -NGC4348,G,12:23:53.99,-03:26:34.5,Vir -NGC4349,OCl,12:24:06.04,-61:52:13.6,Cru -NGC4350,G,12:23:57.87,+16:41:36.3,Com -NGC4351,G,12:24:01.50,+12:12:17.2,Vir -NGC4352,G,12:24:05.02,+11:13:05.0,Vir -NGC4353,G,12:24:00.26,+07:47:06.7,Vir -NGC4354,Dup,12:24:01.50,+12:12:17.2,Vir -NGC4355,G,12:26:54.62,-00:52:39.4,Vir -NGC4356,G,12:24:14.53,+08:32:09.1,Vir -NGC4357,G,12:23:58.82,+48:46:46.1,CVn -NGC4358,G,12:24:02.02,+58:23:06.8,UMa -NGC4359,G,12:24:11.17,+31:31:19.0,Com -NGC4360,G,12:24:21.70,+09:17:34.0,Vir -NGC4360B,G,12:24:14.72,+09:16:00.7,Vir -NGC4361,PN,12:24:30.76,-18:47:05.4,Crv -NGC4362,G,12:24:11.26,+58:21:38.4,UMa -NGC4363,G,12:23:28.40,+74:57:08.0,Dra -NGC4364,Dup,12:24:11.26,+58:21:38.4,UMa -NGC4365,G,12:24:28.28,+07:19:03.6,Vir -NGC4366,G,12:24:47.01,+07:21:10.9,Vir -NGC4367,**,12:24:35.14,+12:10:56.3,Vir -NGC4368,Dup,12:23:06.68,+10:37:16.3,Vir -NGC4369,G,12:24:36.20,+39:22:58.8,CVn -NGC4370,G,12:24:54.90,+07:26:41.6,Vir -NGC4371,G,12:24:55.43,+11:42:15.2,Vir -NGC4372,GCl,12:25:45.38,-72:39:32.7,Mus -NGC4373,G,12:25:17.82,-39:45:35.0,Cen -NGC4373A,G,12:25:37.73,-39:19:10.8,Cen -NGC4373B,G,12:26:43.78,-39:08:03.2,Cen -NGC4374,G,12:25:03.74,+12:53:13.1,Vir -NGC4375,G,12:25:00.47,+28:33:31.0,Com -NGC4376,G,12:25:18.06,+05:44:28.3,Vir -NGC4377,G,12:25:12.34,+14:45:43.8,Com -NGC4378,G,12:25:18.10,+04:55:30.5,Vir -NGC4379,G,12:25:14.74,+15:36:26.9,Com -NGC4380,G,12:25:22.17,+10:01:00.5,Vir -NGC4381,Dup,12:23:58.82,+48:46:46.1,CVn -NGC4382,G,12:25:24.11,+18:11:29.4,Com -NGC4383,G,12:25:25.52,+16:28:12.5,Com -NGC4384,G,12:25:11.98,+54:30:22.4,UMa -NGC4385,G,12:25:42.80,+00:34:21.4,Vir -NGC4386,G,12:24:28.35,+75:31:44.1,Dra -NGC4387,G,12:25:41.68,+12:48:37.9,Vir -NGC4388,G,12:25:46.75,+12:39:43.5,Vir -NGC4389,G,12:25:35.10,+45:41:04.8,CVn -NGC4390,G,12:25:50.67,+10:27:32.6,Vir -NGC4391,G,12:25:18.79,+64:56:00.5,Dra -NGC4392,G,12:25:18.96,+45:50:50.9,CVn -NGC4393,G,12:25:51.23,+27:33:41.6,Com -NGC4394,G,12:25:55.53,+18:12:50.6,Com -NGC4395,G,12:25:48.86,+33:32:48.9,CVn -NGC4396,G,12:25:58.82,+15:40:17.3,Com -NGC4397,Other,12:25:58.16,+18:18:03.7,Com -NGC4398,*,12:26:07.43,+10:41:09.9,Vir -NGC4399,Other,12:25:43.01,+33:30:57.9,CVn -NGC4400,Other,12:25:55.91,+33:30:53.4,CVn -NGC4401,Other,12:25:57.57,+33:31:42.1,CVn -NGC4402,G,12:26:07.65,+13:06:48.0,Vir -NGC4403,G,12:26:12.79,-07:41:05.8,Vir -NGC4404,G,12:26:16.21,-07:40:51.0,Vir -NGC4405,G,12:26:07.15,+16:10:51.6,Com -NGC4406,G,12:26:11.74,+12:56:46.4,Vir -NGC4407,G,12:26:32.25,+12:36:39.5,Vir -NGC4408,G,12:26:17.24,+27:52:16.2,Com -NGC4409,G,12:26:58.50,+02:29:39.7,Vir -NGC4410,GPair,12:26:28.86,+09:01:10.8,Vir -NGC4410A,G,12:26:28.29,+09:01:11.5,Vir -NGC4410B,G,12:26:29.58,+09:01:09.2,Vir -NGC4410C,G,12:26:35.50,+09:02:07.7,Vir -NGC4411,G,12:26:30.10,+08:52:20.0,Vir -NGC4411B,G,12:26:47.23,+08:53:04.6,Vir -NGC4412,G,12:26:36.08,+03:57:52.9,Vir -NGC4413,Dup,12:26:32.25,+12:36:39.5,Vir -NGC4414,G,12:26:27.10,+31:13:24.7,Com -NGC4415,G,12:26:40.49,+08:26:08.5,Vir -NGC4416,G,12:26:46.72,+07:55:08.4,Vir -NGC4417,G,12:26:50.61,+09:35:03.3,Vir -NGC4418,Dup,12:26:54.62,-00:52:39.4,Vir -NGC4419,G,12:26:56.44,+15:02:50.6,Com -NGC4420,Dup,12:26:58.50,+02:29:39.7,Vir -NGC4421,G,12:27:02.54,+15:27:41.3,Com -NGC4422,G,12:27:12.11,-05:49:51.6,Vir -NGC4423,G,12:27:08.97,+05:52:48.6,Vir -NGC4424,G,12:27:11.61,+09:25:14.4,Vir -NGC4425,G,12:27:13.33,+12:44:05.0,Vir -NGC4426,**,12:27:10.53,+27:50:17.5,Com -NGC4427,Dup,12:27:10.53,+27:50:17.5,Com -NGC4428,G,12:27:28.30,-08:10:04.4,Vir -NGC4429,G,12:27:26.51,+11:06:27.8,Vir -NGC4430,G,12:27:26.41,+06:15:46.0,Vir -NGC4431,G,12:27:27.38,+12:17:25.0,Vir -NGC4432,G,12:27:32.97,+06:13:59.7,Vir -NGC4433,G,12:27:38.59,-08:16:42.3,Vir -NGC4434,G,12:27:36.68,+08:09:15.6,Vir -NGC4435,G,12:27:40.49,+13:04:44.2,Vir -NGC4436,G,12:27:41.24,+12:18:57.2,Vir -NGC4437,Dup,12:32:45.59,+00:06:54.1,Vir -NGC4438,G,12:27:45.59,+13:00:31.8,Vir -NGC4439,OCl,12:28:26.35,-60:06:11.6,Cru -NGC4440,G,12:27:53.57,+12:17:35.8,Vir -NGC4441,G,12:27:20.35,+64:48:05.4,Dra -NGC4442,G,12:28:03.88,+09:48:13.4,Vir -NGC4443,Dup,12:29:03.01,+13:11:01.5,Vir -NGC4444,G,12:28:36.41,-43:15:42.3,Cen -NGC4445,G,12:28:15.93,+09:26:10.3,Vir -NGC4446,G,12:28:06.79,+13:54:42.3,Com -NGC4447,G,12:28:12.53,+13:53:57.2,Com -NGC4448,G,12:28:15.43,+28:37:13.1,Com -NGC4449,G,12:28:11.10,+44:05:37.1,CVn -NGC4450,G,12:28:29.63,+17:05:05.8,Com -NGC4451,G,12:28:40.55,+09:15:31.7,Vir -NGC4452,G,12:28:43.31,+11:45:18.1,Vir -NGC4453,GPair,12:28:47.20,+06:30:27.0,Vir -NGC4453 NED01,G,12:28:46.81,+06:30:43.2,Vir -NGC4453 NED02,G,12:28:47.68,+06:30:12.8,Vir -NGC4454,G,12:28:50.75,-01:56:21.1,Vir -NGC4455,G,12:28:44.11,+22:49:13.6,Com -NGC4456,G,12:27:52.40,-30:05:52.4,Hya -NGC4457,G,12:28:59.01,+03:34:14.1,Vir -NGC4458,G,12:28:57.57,+13:14:30.8,Vir -NGC4459,G,12:29:00.01,+13:58:42.1,Com -NGC4460,G,12:28:45.56,+44:51:51.2,CVn -NGC4461,G,12:29:03.01,+13:11:01.5,Vir -NGC4462,G,12:29:21.22,-23:09:58.9,Crv -NGC4463,OCl,12:29:55.22,-64:47:22.8,Mus -NGC4464,G,12:29:21.29,+08:09:23.8,Vir -NGC4465,G,12:29:23.54,+08:01:33.6,Vir -NGC4466,G,12:29:30.57,+07:41:47.1,Vir -NGC4467,G,12:29:30.25,+07:59:34.3,Vir -NGC4468,G,12:29:30.90,+14:02:56.7,Com -NGC4469,G,12:29:28.03,+08:44:59.7,Vir -NGC4470,G,12:29:37.78,+07:49:27.1,Vir -NGC4471,G,12:29:37.05,+07:55:57.9,Vir -NGC4472,G,12:29:46.76,+08:00:01.7,Vir -NGC4473,G,12:29:48.87,+13:25:45.7,Com -NGC4474,G,12:29:53.55,+14:04:06.9,Com -NGC4475,G,12:29:47.58,+27:14:36.0,Com -NGC4476,G,12:29:59.08,+12:20:55.2,Vir -NGC4477,G,12:30:02.20,+13:38:11.8,Com -NGC4478,G,12:30:17.42,+12:19:42.8,Vir -NGC4479,G,12:30:18.37,+13:34:39.5,Com -NGC4480,G,12:30:26.78,+04:14:47.7,Vir -NGC4481,G,12:29:48.67,+64:01:58.8,Dra -NGC4482,G,12:30:10.33,+10:46:46.1,Vir -NGC4483,G,12:30:40.65,+09:00:56.4,Vir -NGC4484,G,12:28:52.68,-11:39:07.4,Vir -NGC4485,G,12:30:31.13,+41:42:04.2,CVn -NGC4486,G,12:30:49.42,+12:23:28.0,Vir -NGC4486A,G,12:30:57.71,+12:16:13.3,Vir -NGC4486B,G,12:30:31.97,+12:29:24.6,Vir -NGC4487,G,12:31:04.46,-08:03:14.1,Vir -NGC4488,G,12:30:51.37,+08:21:36.0,Vir -NGC4489,G,12:30:52.25,+16:45:31.9,Com -NGC4490,G,12:30:36.24,+41:38:38.0,CVn -NGC4491,G,12:30:57.11,+11:29:00.7,Vir -NGC4492,G,12:30:59.71,+08:04:40.3,Vir -NGC4493,G,12:31:08.37,+00:36:49.3,Vir -NGC4494,G,12:31:24.10,+25:46:30.9,Com -NGC4495,G,12:31:22.88,+29:08:11.3,Com -NGC4496,GPair,12:31:40.00,+03:55:58.0,Vir -NGC4496A,G,12:31:39.21,+03:56:22.1,Vir -NGC4496B,G,12:31:40.88,+03:55:34.8,Vir -NGC4497,G,12:31:32.53,+11:37:29.0,Vir -NGC4498,G,12:31:39.56,+16:51:10.0,Com -NGC4499,G,12:32:04.94,-39:58:56.9,Cen -NGC4500,G,12:31:22.16,+57:57:52.7,UMa -NGC4501,G,12:31:59.16,+14:25:13.4,Com -NGC4502,G,12:32:03.35,+16:41:15.8,Com -NGC4503,G,12:32:06.23,+11:10:35.1,Vir -NGC4504,G,12:32:17.45,-07:33:48.4,Vir -NGC4505,Dup,12:31:39.21,+03:56:22.1,Vir -NGC4506,G,12:32:10.53,+13:25:10.6,Com -NGC4507,G,12:35:36.63,-39:54:33.3,Cen -NGC4508,**,12:32:17.42,+05:49:11.9,Vir -NGC4509,G,12:33:06.79,+32:05:29.6,CVn -NGC4510,G,12:31:47.21,+64:14:01.7,Dra -NGC4511,G,12:32:08.11,+56:28:16.2,UMa -NGC4512,G,12:32:47.65,+63:56:21.1,Dra -NGC4513,G,12:32:01.52,+66:19:57.3,Dra -NGC4514,G,12:32:42.97,+29:42:44.6,Com -NGC4515,G,12:33:04.97,+16:15:55.9,Com -NGC4516,G,12:33:07.54,+14:34:29.8,Com -NGC4517,G,12:32:45.59,+00:06:54.1,Vir -NGC4517A,G,12:32:28.15,+00:23:22.8,Vir -NGC4518,G,12:33:11.76,+07:51:05.9,Vir -NGC4519,G,12:33:30.25,+08:39:17.1,Vir -NGC4519A,G,12:33:24.71,+08:41:26.7,Vir -NGC4520,G,12:33:49.90,-07:22:32.0,Vir -NGC4521,Dup,12:32:47.65,+63:56:21.1,Dra -NGC4522,G,12:33:39.71,+09:10:30.1,Vir -NGC4523,G,12:33:48.00,+15:10:05.9,Com -NGC4524,G,12:33:54.39,-12:01:39.4,Crv -NGC4525,G,12:33:51.14,+30:16:38.9,Com -NGC4526,G,12:34:03.09,+07:41:58.3,Vir -NGC4527,G,12:34:08.42,+02:39:13.2,Vir -NGC4528,G,12:34:06.07,+11:19:16.5,Vir -NGC4529,G,12:32:51.65,+20:11:00.6,Com -NGC4530,*,12:33:47.71,+41:21:12.5,CVn -NGC4531,G,12:34:15.89,+13:04:31.2,Vir -NGC4532,G,12:34:19.33,+06:28:03.7,Vir -NGC4533,G,12:34:22.02,+02:19:31.3,Vir -NGC4534,G,12:34:05.42,+35:31:06.0,CVn -NGC4535,G,12:34:20.31,+08:11:51.9,Vir -NGC4536,G,12:34:27.05,+02:11:17.3,Vir -NGC4537,G,12:34:48.91,+50:48:18.6,CVn -NGC4538,G,12:34:40.90,+03:19:25.2,Vir -NGC4539,G,12:34:34.74,+18:12:09.7,Com -NGC4540,G,12:34:50.87,+15:33:06.2,Com -NGC4541,G,12:35:10.66,-00:13:16.1,Vir -NGC4542,Dup,12:34:48.91,+50:48:18.6,CVn -NGC4543,G,12:35:20.24,+06:06:54.3,Vir -NGC4544,G,12:35:36.57,+03:02:04.3,Vir -NGC4545,G,12:34:34.15,+63:31:30.2,Dra -NGC4546,G,12:35:29.51,-03:47:35.5,Vir -NGC4547,G,12:34:51.84,+58:55:00.4,UMa -NGC4548,G,12:35:26.45,+14:29:46.8,Com -NGC4549,G,12:35:21.27,+58:56:58.8,UMa -NGC4550,G,12:35:30.58,+12:13:15.0,Vir -NGC4551,G,12:35:37.95,+12:15:50.3,Vir -NGC4552,G,12:35:39.81,+12:33:22.8,Vir -NGC4553,G,12:36:07.60,-39:26:19.2,Cen -NGC4554,**,12:35:59.52,+11:15:55.3,Vir -NGC4555,G,12:35:41.18,+26:31:23.2,Com -NGC4556,G,12:35:45.86,+26:54:32.0,Com -NGC4557,Other,12:35:49.74,+27:03:13.6,Com -NGC4558,G,12:35:52.65,+26:59:31.7,Com -NGC4559,G,12:35:57.65,+27:57:36.0,Com -NGC4559A,G,12:36:53.44,+27:51:43.3,Com -NGC4559B,G,12:36:53.93,+27:44:56.8,Com -NGC4559C,HII,12:35:52.12,+27:55:55.5,Com -NGC4560,Dup,12:34:03.09,+07:41:58.3,Vir -NGC4561,G,12:36:08.20,+19:19:22.5,Com -NGC4562,G,12:35:34.80,+25:51:00.0,Com -NGC4563,G,12:36:12.88,+26:56:29.2,Com -NGC4564,G,12:36:26.98,+11:26:21.4,Vir -NGC4565,G,12:36:20.78,+25:59:15.6,Com -NGC4565A,Dup,12:35:34.80,+25:51:00.0,Com -NGC4565B,G,12:35:41.69,+26:13:19.9,Com -NGC4565C,G,12:35:41.42,+26:17:09.0,Com -NGC4566,G,12:36:00.11,+54:13:15.5,UMa -NGC4567,G,12:36:32.71,+11:15:28.8,Vir -NGC4568,G,12:36:34.26,+11:14:20.0,Vir -NGC4569,G,12:36:49.79,+13:09:46.6,Vir -NGC4570,G,12:36:53.40,+07:14:47.9,Vir -NGC4571,G,12:36:56.38,+14:13:02.5,Com -NGC4572,G,12:35:45.30,+74:14:44.0,Dra -NGC4573,G,12:37:43.79,-43:37:16.1,Cen -NGC4574,G,12:37:43.51,-35:31:03.5,Cen -NGC4575,G,12:37:51.12,-40:32:14.5,Cen -NGC4576,G,12:37:33.56,+04:22:03.7,Vir -NGC4577,G,12:39:12.44,+06:00:44.3,Vir -NGC4578,G,12:37:30.56,+09:33:18.3,Vir -NGC4579,G,12:37:43.52,+11:49:05.5,Vir -NGC4580,G,12:37:48.39,+05:22:06.7,Vir -NGC4581,G,12:38:05.17,+01:28:39.9,Vir -NGC4582,*,12:38:10.20,+00:10:57.9,Vir -NGC4583,G,12:38:04.54,+33:27:31.8,CVn -NGC4584,G,12:38:17.87,+13:06:35.6,Vir -NGC4585,G,12:38:13.28,+28:56:13.3,Com -NGC4586,G,12:38:28.40,+04:19:08.7,Vir -NGC4587,G,12:38:35.42,+02:39:26.4,Vir -NGC4588,G,12:38:45.43,+06:46:05.4,Vir -NGC4589,G,12:37:24.99,+74:11:30.9,Dra -NGC4590,GCl,12:39:28.01,-26:44:34.9,Hya -NGC4591,Dup,12:39:12.44,+06:00:44.3,Vir -NGC4592,G,12:39:18.74,-00:31:55.2,Vir -NGC4593,G,12:39:39.43,-05:20:39.3,Vir -NGC4594,G,12:39:59.43,-11:37:23.0,Vir -NGC4595,G,12:39:51.91,+15:17:52.1,Com -NGC4596,G,12:39:55.95,+10:10:34.1,Vir -NGC4597,G,12:40:12.93,-05:47:57.4,Vir -NGC4598,G,12:40:11.93,+08:23:01.5,Vir -NGC4599,G,12:40:27.09,+01:11:48.9,Vir -NGC4600,G,12:40:22.96,+03:07:03.9,Vir -NGC4601,G,12:40:46.74,-40:53:35.0,Cen -NGC4602,G,12:40:36.85,-05:07:58.8,Vir -NGC4603,G,12:40:55.21,-40:58:35.0,Cen -NGC4603A,G,12:39:36.92,-40:44:24.1,Cen -NGC4603B,G,12:40:29.74,-41:04:11.4,Cen -NGC4603C,G,12:40:43.11,-40:45:48.1,Cen -NGC4603D,G,12:42:08.10,-40:49:15.1,Cen -NGC4604,G,12:40:45.06,-05:18:10.4,Vir -NGC4605,G,12:39:59.38,+61:36:33.1,UMa -NGC4606,G,12:40:57.54,+11:54:44.0,Vir -NGC4607,G,12:41:12.40,+11:53:11.9,Vir -NGC4608,G,12:41:13.29,+10:09:20.4,Vir -NGC4609,OCl,12:42:16.83,-62:59:44.7,Cru -NGC4610,Dup,12:29:37.78,+07:49:27.1,Vir -NGC4611,G,12:41:25.45,+13:43:46.3,Com -NGC4612,G,12:41:32.75,+07:18:53.6,Vir -NGC4613,G,12:41:28.95,+26:05:19.0,Com -NGC4614,G,12:41:31.47,+26:02:33.6,Com -NGC4615,G,12:41:37.32,+26:04:22.2,Com -NGC4616,G,12:42:16.45,-40:38:31.4,Cen -NGC4617,G,12:41:05.87,+50:23:36.3,CVn -NGC4618,G,12:41:32.85,+41:09:02.8,CVn -NGC4619,G,12:41:44.55,+35:03:46.0,CVn -NGC4620,G,12:41:59.36,+12:56:34.0,Vir -NGC4621,G,12:42:02.24,+11:38:49.3,Vir -NGC4622,G,12:42:37.62,-40:44:39.2,Cen -NGC4622A,G,12:43:49.11,-40:42:52.6,Cen -NGC4622B,G,12:43:50.61,-40:43:02.9,Cen -NGC4623,G,12:42:10.69,+07:40:37.0,Vir -NGC4624,Dup,12:45:05.99,+03:03:20.8,Vir -NGC4625,G,12:41:52.72,+41:16:26.3,CVn -NGC4626,G,12:42:25.31,-07:02:39.4,Vir -NGC4627,G,12:41:59.68,+32:34:24.8,CVn -NGC4628,G,12:42:25.26,-06:58:15.6,Vir -NGC4629,G,12:42:32.67,-01:21:02.4,Vir -NGC4630,G,12:42:31.13,+03:57:36.9,Vir -NGC4631,G,12:42:08.01,+32:32:29.4,CVn -NGC4632,G,12:42:32.03,-00:04:57.4,Vir -NGC4633,G,12:42:37.38,+14:21:25.9,Com -NGC4634,G,12:42:40.96,+14:17:45.0,Com -NGC4635,G,12:42:39.24,+19:56:43.1,Com -NGC4636,G,12:42:49.83,+02:41:16.0,Vir -NGC4637,G,12:42:54.10,+11:26:17.8,Vir -NGC4638,G,12:42:47.42,+11:26:33.0,Vir -NGC4639,G,12:42:52.39,+13:15:26.6,Vir -NGC4640,G,12:42:57.77,+12:17:13.1,Vir -NGC4641,G,12:43:07.66,+12:03:04.3,Vir -NGC4642,G,12:43:17.78,-00:38:39.3,Vir -NGC4643,G,12:43:20.14,+01:58:41.8,Vir -NGC4644,G,12:42:42.66,+55:08:43.8,UMa -NGC4645,G,12:44:09.99,-41:44:59.8,Cen -NGC4645A,G,12:43:05.57,-41:21:32.1,Cen -NGC4645B,G,12:43:31.18,-41:21:45.1,Cen -NGC4646,G,12:42:52.12,+54:51:21.6,UMa -NGC4647,G,12:43:32.31,+11:34:54.7,Vir -NGC4648,G,12:41:44.39,+74:25:15.4,Dra -NGC4649,G,12:43:39.98,+11:33:09.7,Vir -NGC4650,G,12:44:19.59,-40:43:54.5,Cen -NGC4650A,G,12:44:49.06,-40:42:51.5,Cen -NGC4651,G,12:43:42.63,+16:23:36.2,Com -NGC4652,G,12:43:19.75,+58:57:53.6,UMa -NGC4653,G,12:43:50.91,-00:33:40.4,Vir -NGC4654,G,12:43:56.58,+13:07:36.0,Vir -NGC4655,G,12:43:36.49,+41:01:06.7,CVn -NGC4656,GPair,12:43:57.73,+32:10:05.3,CVn -NGC4656 NED01,G,12:43:57.67,+32:10:12.9,CVn -NGC4656 NED02,Dup,12:44:07.10,+32:12:31.5,CVn -NGC4657,G,12:44:07.10,+32:12:31.5,CVn -NGC4658,G,12:44:37.79,-10:04:59.2,Vir -NGC4659,G,12:44:29.39,+13:29:54.8,Com -NGC4660,G,12:44:31.98,+11:11:25.9,Vir -NGC4661,G,12:45:14.95,-40:49:27.3,Cen -NGC4662,G,12:44:26.20,+37:07:16.4,CVn -NGC4663,G,12:44:47.05,-10:11:52.2,Vir -NGC4664,G,12:45:05.99,+03:03:20.8,Vir -NGC4665,Dup,12:45:05.99,+03:03:20.8,Vir -NGC4666,G,12:45:08.59,-00:27:42.8,Vir -NGC4667,Dup,12:42:47.42,+11:26:33.0,Vir -NGC4668,G,12:45:31.99,-00:32:08.6,Vir -NGC4669,G,12:44:46.74,+54:52:32.4,UMa -NGC4670,G,12:45:17.07,+27:07:31.5,Com -NGC4671,G,12:45:47.63,-07:04:10.9,Vir -NGC4672,G,12:46:15.75,-41:42:21.5,Cen -NGC4673,G,12:45:34.67,+27:03:39.0,Com -NGC4674,G,12:46:03.47,-08:39:19.6,Vir -NGC4675,G,12:45:31.89,+54:44:15.4,UMa -NGC4676,GPair,12:46:10.70,+30:43:38.0,Com -NGC4676A,G,12:46:10.11,+30:43:54.9,Com -NGC4676B,G,12:46:11.24,+30:43:21.9,Com -NGC4677,G,12:46:57.13,-41:34:57.1,Cen -NGC4678,G,12:49:41.85,-04:34:47.0,Vir -NGC4679,G,12:47:30.25,-39:34:15.1,Cen -NGC4680,G,12:46:54.71,-11:38:13.4,Vir -NGC4681,G,12:47:28.81,-43:20:05.3,Cen -NGC4682,G,12:47:15.49,-10:03:48.4,Vir -NGC4683,G,12:47:42.37,-41:31:41.8,Cen -NGC4684,G,12:47:17.52,-02:43:38.7,Vir -NGC4685,G,12:47:11.44,+19:27:51.7,Com -NGC4686,G,12:46:39.88,+54:32:03.4,UMa -NGC4687,G,12:47:23.77,+35:21:07.4,CVn -NGC4688,G,12:47:46.52,+04:20:09.8,Vir -NGC4689,G,12:47:45.56,+13:45:46.1,Com -NGC4690,G,12:47:55.52,-01:39:21.8,Vir -NGC4691,G,12:48:13.63,-03:19:57.8,Vir -NGC4692,G,12:47:55.32,+27:13:20.5,Com -NGC4693,G,12:47:09.17,+71:10:34.1,Dra -NGC4694,G,12:48:15.08,+10:59:01.0,Vir -NGC4695,G,12:47:32.11,+54:22:29.4,UMa -NGC4696,G,12:48:49.25,-41:18:39.0,Cen -NGC4696A,G,12:46:55.62,-41:29:48.3,Cen -NGC4696B,G,12:47:21.78,-41:14:15.0,Cen -NGC4696C,G,12:48:02.66,-40:49:06.8,Cen -NGC4696D,G,12:48:21.66,-41:42:50.5,Cen -NGC4696E,G,12:48:26.12,-40:56:11.4,Cen -NGC4697,G,12:48:35.88,-05:48:02.7,Vir -NGC4698,G,12:48:22.91,+08:29:14.6,Vir -NGC4699,G,12:49:02.23,-08:39:53.5,Vir -NGC4700,G,12:49:08.15,-11:24:35.5,Vir -NGC4701,G,12:49:11.59,+03:23:19.4,Vir -NGC4702,G,12:49:01.49,+27:10:44.9,Com -NGC4703,G,12:49:18.97,-09:06:30.3,Vir -NGC4704,G,12:48:46.43,+41:55:16.5,CVn -NGC4705,G,12:49:24.97,-05:11:45.1,Vir -NGC4706,G,12:49:54.15,-41:16:46.4,Cen -NGC4707,G,12:48:22.87,+51:09:52.9,CVn -NGC4708,G,12:49:41.48,-11:05:34.9,Vir -NGC4709,G,12:50:03.88,-41:22:55.1,Cen -NGC4710,G,12:49:38.83,+15:09:55.6,Com -NGC4711,G,12:48:45.87,+35:19:57.7,CVn -NGC4712,G,12:49:34.22,+25:28:11.8,Com -NGC4713,G,12:49:57.87,+05:18:41.1,Vir -NGC4714,G,12:50:19.25,-13:19:27.6,Crv -NGC4715,G,12:49:57.88,+27:49:20.7,Com -NGC4716,G,12:50:33.11,-09:27:04.0,Vir -NGC4717,G,12:50:34.39,-09:27:46.9,Vir -NGC4718,G,12:50:32.64,-05:16:54.9,Vir -NGC4719,G,12:50:08.72,+33:09:32.9,CVn -NGC4720,G,12:50:42.78,-04:09:21.0,Vir -NGC4721,G,12:50:19.92,+27:19:26.4,Com -NGC4722,G,12:51:32.39,-13:19:48.1,Crv -NGC4723,G,12:51:02.91,-13:14:11.8,Crv -NGC4724,G,12:50:53.84,-14:19:54.7,Crv -NGC4725,G,12:50:26.58,+25:30:02.9,Com -NGC4726,G,12:50:46.06,-14:16:06.8,Crv -NGC4727,G,12:50:57.24,-14:19:58.6,Crv -NGC4728,G,12:50:28.08,+27:26:05.7,Com -NGC4729,G,12:51:46.28,-41:07:56.2,Cen -NGC4730,G,12:52:00.47,-41:08:50.3,Cen -NGC4731,G,12:51:01.09,-06:23:35.0,Vir -NGC4732,G,12:50:07.10,+52:51:00.4,UMa -NGC4733,G,12:51:06.78,+10:54:43.5,Vir -NGC4734,G,12:51:12.89,+04:51:32.6,Vir -NGC4735,G,12:51:01.73,+28:55:40.7,Com -NGC4736,G,12:50:53.06,+41:07:13.6,CVn -NGC4737,G,12:50:52.94,+34:09:24.7,CVn -NGC4738,G,12:51:08.90,+28:47:17.0,Com -NGC4739,G,12:51:37.08,-08:24:36.8,Vir -NGC4740,Dup,12:50:57.24,-14:19:58.6,Crv -NGC4741,G,12:50:59.52,+47:40:17.6,CVn -NGC4742,G,12:51:48.04,-10:27:17.0,Vir -NGC4743,G,12:52:15.98,-41:23:26.4,Cen -NGC4744,G,12:52:19.60,-41:03:36.1,Cen -NGC4745,G,12:51:26.16,+27:25:16.6,Com -NGC4746,G,12:51:55.36,+12:04:58.7,Vir -NGC4747,G,12:51:45.95,+25:46:37.5,Com -NGC4748,G,12:52:12.46,-13:24:53.0,Crv -NGC4749,G,12:51:11.97,+71:38:12.2,Dra -NGC4750,G,12:50:07.27,+72:52:28.7,Dra -NGC4751,G,12:52:50.79,-42:39:35.7,Cen -NGC4752,G,12:51:29.07,+13:46:54.5,Com -NGC4753,G,12:52:22.11,-01:11:58.9,Vir -NGC4754,G,12:52:17.50,+11:18:50.0,Vir -NGC4755,OCl,12:53:37.08,-60:21:22.7,Cru -NGC4756,G,12:52:52.63,-15:24:47.8,Crv -NGC4757,G,12:52:50.09,-10:18:36.8,Vir -NGC4758,G,12:52:44.05,+15:50:54.7,Com -NGC4759,GPair,12:53:05.20,-09:12:08.0,Vir -NGC4759 NED01,Dup,12:53:04.46,-09:11:59.7,Vir -NGC4759 NED02,Dup,12:53:05.81,-09:12:13.8,Vir -NGC4760,G,12:53:07.23,-10:29:39.1,Vir -NGC4761,G,12:53:09.81,-09:11:52.1,Vir -NGC4762,G,12:52:56.05,+11:13:50.9,Vir -NGC4763,G,12:53:27.22,-17:00:19.9,Crv -NGC4764,G,12:53:06.63,-09:15:27.5,Vir -NGC4765,G,12:53:14.41,+04:27:47.2,Vir -NGC4766,G,12:53:08.17,-10:22:41.5,Vir -NGC4767,G,12:53:52.95,-39:42:51.5,Cen -NGC4767A,G,12:53:01.48,-39:50:06.9,Cen -NGC4767B,G,12:54:45.03,-39:51:08.2,Cen -NGC4768,*,12:53:17.25,-09:31:53.2,Vir -NGC4769,**,12:53:18.08,-09:32:09.6,Vir -NGC4770,G,12:53:32.15,-09:32:29.4,Vir -NGC4771,G,12:53:21.22,+01:16:08.7,Vir -NGC4772,G,12:53:29.16,+02:10:06.2,Vir -NGC4773,G,12:53:36.01,-08:38:19.5,Vir -NGC4774,GPair,12:53:06.20,+36:49:22.0,CVn -NGC4774 NED01,G,12:53:06.02,+36:49:34.8,CVn -NGC4774 NED02,G,12:53:06.59,+36:49:08.6,CVn -NGC4775,G,12:53:45.70,-06:37:19.8,Vir -NGC4776,G,12:53:04.46,-09:11:59.7,Vir -NGC4777,G,12:53:58.54,-08:46:32.6,Vir -NGC4778,G,12:53:05.81,-09:12:13.8,Vir -NGC4779,G,12:53:50.84,+09:42:36.2,Vir -NGC4780,G,12:54:05.23,-08:37:16.0,Vir -NGC4780A,G,12:54:03.04,-08:39:13.7,Vir -NGC4781,G,12:54:23.75,-10:32:13.9,Vir -NGC4782,G,12:54:35.72,-12:34:07.1,Crv -NGC4783,G,12:54:36.59,-12:33:28.2,Crv -NGC4784,G,12:54:37.02,-10:36:47.0,Vir -NGC4785,G,12:53:27.33,-48:44:57.1,Cen -NGC4786,G,12:54:32.42,-06:51:33.9,Vir -NGC4787,G,12:54:05.52,+27:04:07.1,Com -NGC4788,G,12:54:16.03,+27:18:13.3,Com -NGC4789,G,12:54:19.02,+27:04:04.9,Com -NGC4789A,G,12:54:05.25,+27:08:58.7,Com -NGC4790,G,12:54:51.94,-10:14:52.2,Vir -NGC4791,G,12:54:43.95,+08:03:11.2,Vir -NGC4792,G,12:55:03.68,-12:29:49.8,Crv -NGC4793,G,12:54:40.62,+28:56:19.2,Com -NGC4794,G,12:55:10.50,-12:36:30.5,Crv -NGC4795,G,12:55:02.86,+08:03:55.8,Vir -NGC4796,G,12:55:04.69,+08:03:58.4,Vir -NGC4797,G,12:54:55.17,+27:24:45.7,Com -NGC4798,Dup,12:54:55.17,+27:24:45.7,Com -NGC4799,G,12:55:15.53,+02:53:47.9,Vir -NGC4800,G,12:54:37.80,+46:31:52.2,CVn -NGC4801,G,12:54:37.77,+53:05:24.0,UMa -NGC4802,G,12:55:49.64,-12:03:19.1,Crv -NGC4803,G,12:55:33.68,+08:14:25.5,Vir -NGC4804,Dup,12:55:49.64,-12:03:19.1,Crv -NGC4805,*,12:55:24.23,+27:58:52.7,Com -NGC4806,G,12:56:12.40,-29:30:10.6,Hya -NGC4807,G,12:55:29.10,+27:31:17.1,Com -NGC4808,G,12:55:48.95,+04:18:14.8,Vir -NGC4809,G,12:54:51.06,+02:39:14.7,Vir -NGC4810,G,12:54:51.20,+02:38:25.0,Vir -NGC4811,G,12:56:52.36,-41:47:49.9,Cen -NGC4812,G,12:56:52.70,-41:48:48.0,Cen -NGC4813,G,12:56:36.12,-06:49:04.2,Vir -NGC4814,G,12:55:21.94,+58:20:38.8,UMa -NGC4815,OCl,12:57:58.37,-64:57:42.3,Mus -NGC4816,G,12:56:12.15,+27:44:43.7,Com -NGC4817,G,12:56:29.82,+27:56:24.0,Com -NGC4818,G,12:56:48.90,-08:31:31.1,Vir -NGC4819,G,12:56:27.86,+26:59:14.4,Com -NGC4820,G,12:57:00.56,-13:43:09.6,Vir -NGC4821,G,12:56:29.13,+26:57:25.2,Com -NGC4822,G,12:57:03.74,-10:45:42.6,Vir -NGC4823,G,12:57:25.64,-13:41:55.3,Vir -NGC4824,*,12:56:36.37,+27:25:57.5,Com -NGC4825,G,12:57:12.24,-13:39:53.4,Vir -NGC4826,G,12:56:43.64,+21:40:58.7,Com -NGC4827,G,12:56:43.53,+27:10:43.1,Com -NGC4828,G,12:56:42.86,+28:01:13.6,Com -NGC4829,G,12:57:24.48,-13:44:15.0,Vir -NGC4830,G,12:57:27.91,-19:41:28.6,Vir -NGC4831,G,12:57:36.69,-27:17:31.7,Hya -NGC4832,G,12:57:47.53,-39:45:42.1,Cen -NGC4833,GCl,12:59:34.94,-70:52:28.5,Mus -NGC4834,G,12:56:25.28,+52:17:45.1,CVn -NGC4835,G,12:58:07.84,-46:15:51.2,Cen -NGC4835A,G,12:57:13.13,-46:22:28.9,Cen -NGC4836,G,12:57:34.29,-12:44:38.8,Vir -NGC4837,GPair,12:56:49.10,+48:17:55.0,CVn -NGC4837 NED01,G,12:56:48.30,+48:17:48.8,CVn -NGC4837 NED02,G,12:56:49.90,+48:18:00.4,CVn -NGC4838,G,12:57:56.16,-13:03:36.3,Vir -NGC4839,G,12:57:24.36,+27:29:52.1,Com -NGC4840,G,12:57:32.84,+27:36:37.2,Com -NGC4841,GPair,12:57:32.90,+28:28:46.0,Com -NGC4841A,G,12:57:31.95,+28:28:37.0,Com -NGC4841B,G,12:57:33.89,+28:28:56.1,Com -NGC4842,GPair,12:57:35.90,+27:29:21.0,Com -NGC4842A,G,12:57:35.84,+27:29:35.7,Com -NGC4842B,G,12:57:36.14,+27:29:05.3,Com -NGC4843,G,12:58:00.82,-03:37:16.1,Vir -NGC4844,*,12:58:08.26,-13:04:47.7,Vir -NGC4845,G,12:58:01.19,+01:34:33.0,Vir -NGC4846,G,12:57:47.69,+36:22:14.6,CVn -NGC4847,G,12:58:28.95,-13:08:27.3,Vir -NGC4848,G,12:58:05.64,+28:14:33.9,Com -NGC4849,G,12:58:12.68,+26:23:48.8,Com -NGC4850,G,12:58:21.84,+27:58:03.8,Com -NGC4851,G,12:58:21.71,+28:08:55.5,Com -NGC4852,OCl,13:00:04.39,-59:36:34.0,Cen -NGC4853,G,12:58:35.19,+27:35:46.8,Com -NGC4854,G,12:58:47.40,+27:40:29.1,Com -NGC4855,G,12:59:18.45,-13:13:51.7,Vir -NGC4856,G,12:59:21.27,-15:02:31.8,Vir -NGC4857,G,12:57:18.34,+70:12:12.4,Dra -NGC4858,G,12:59:02.04,+28:06:56.7,Com -NGC4859,G,12:59:01.83,+26:48:56.4,Com -NGC4860,G,12:59:03.90,+28:07:25.3,Com -NGC4861,G,12:59:02.34,+34:51:34.0,CVn -NGC4862,G,12:59:30.82,-14:07:56.4,Vir -NGC4863,G,12:59:42.46,-14:01:46.7,Vir -NGC4864,G,12:59:13.11,+27:58:37.2,Com -NGC4865,G,12:59:19.87,+28:05:03.5,Com -NGC4866,G,12:59:27.14,+14:10:15.8,Vir -NGC4867,G,12:59:15.25,+27:58:14.6,Com -NGC4868,G,12:59:08.90,+37:18:37.2,CVn -NGC4869,G,12:59:23.36,+27:54:41.7,Com -NGC4870,G,12:59:17.77,+37:02:54.1,CVn -NGC4871,G,12:59:29.96,+27:57:23.2,Com -NGC4872,G,12:59:34.04,+27:56:49.0,Com -NGC4873,G,12:59:32.79,+27:59:01.0,Com -NGC4874,G,12:59:35.71,+27:57:33.4,Com -NGC4875,G,12:59:37.91,+27:54:26.4,Com -NGC4876,G,12:59:44.41,+27:54:44.9,Com -NGC4877,G,13:00:26.34,-15:17:00.4,Vir -NGC4878,G,13:00:20.17,-06:06:13.8,Vir -NGC4879,*,13:00:25.61,-06:06:40.4,Vir -NGC4880,G,13:00:10.57,+12:28:59.9,Vir -NGC4881,G,12:59:57.60,+28:14:50.6,Com -NGC4882,Dup,13:00:04.44,+27:59:15.3,Com -NGC4883,G,12:59:56.01,+28:02:05.0,Com -NGC4884,Dup,13:00:08.13,+27:58:37.2,Com -NGC4885,G,13:00:33.87,-06:51:11.1,Vir -NGC4886,G,13:00:04.44,+27:59:15.3,Com -NGC4887,G,13:00:39.26,-14:39:58.5,Vir -NGC4888,G,13:00:36.26,-06:04:30.7,Vir -NGC4889,G,13:00:08.13,+27:58:37.2,Com -NGC4890,G,13:00:39.12,-04:36:15.6,Vir -NGC4891,*,13:00:46.99,-13:25:33.4,Vir -NGC4892,G,13:00:03.53,+26:53:53.2,Com -NGC4893,G,12:59:59.61,+37:11:36.2,CVn -NGC4893A,G,12:59:59.84,+37:11:17.3,CVn -NGC4894,G,13:00:16.52,+27:58:03.1,Com -NGC4895,G,13:00:17.93,+28:12:08.3,Com -NGC4895A,G,13:00:09.12,+28:10:13.4,Com -NGC4896,G,13:00:30.78,+28:20:46.4,Com -NGC4897,G,13:00:52.95,-13:26:59.3,Vir -NGC4898,GPair,13:00:18.00,+27:57:26.0,Com -NGC4898A,G,13:00:17.67,+27:57:19.4,Com -NGC4898B,G,13:00:18.09,+27:57:23.6,Com -NGC4899,G,13:00:56.63,-13:56:39.2,Vir -NGC4900,G,13:00:39.11,+02:30:05.2,Vir -NGC4901,G,12:59:56.41,+47:12:20.0,CVn -NGC4902,G,13:00:59.73,-14:30:49.1,Vir -NGC4903,G,13:01:22.77,-30:56:05.9,Cen -NGC4904,G,13:00:58.66,-00:01:39.4,Vir -NGC4905,G,13:01:30.68,-30:52:05.8,Cen -NGC4906,G,13:00:39.77,+27:55:26.0,Com -NGC4907,G,13:00:48.80,+28:09:30.0,Com -NGC4908,G,13:00:54.46,+28:00:27.5,Com -NGC4909,G,13:02:01.85,-42:46:17.1,Cen -NGC4910,Dup,12:58:01.19,+01:34:33.0,Vir -NGC4911,G,13:00:56.08,+27:47:27.0,Com -NGC4912,G,13:01:33.60,+29:07:50.2,Com -NGC4913,G,13:01:43.37,+29:02:40.8,Com -NGC4914,G,13:00:42.95,+37:18:55.0,CVn -NGC4915,G,13:01:28.22,-04:32:47.5,Vir -NGC4916,G,13:02:04.20,+29:15:12.4,Com -NGC4917,G,13:00:55.57,+47:13:19.6,CVn -NGC4918,G,13:01:50.62,-04:30:02.0,Vir -NGC4919,G,13:01:17.59,+27:48:32.7,Com -NGC4920,G,13:02:04.15,-11:22:42.3,Vir -NGC4921,G,13:01:26.15,+27:53:09.4,Com -NGC4922,GPair,13:01:24.90,+29:18:40.0,Com -NGC4922 NED01,G,13:01:24.52,+29:18:30.1,Com -NGC4922 NED02,G,13:01:25.26,+29:18:49.6,Com -NGC4923,G,13:01:31.80,+27:50:51.1,Com -NGC4924,G,13:02:12.85,-14:58:11.4,Vir -NGC4925,G,13:02:07.39,-07:42:38.8,Vir -NGC4926,G,13:01:53.73,+27:37:28.0,Com -NGC4926A,G,13:02:07.88,+27:38:53.9,Com -NGC4927,G,13:01:57.60,+28:00:21.2,Com -NGC4928,G,13:03:00.57,-08:05:05.9,Vir -NGC4929,G,13:02:44.40,+28:02:43.3,Com -NGC4930,G,13:04:05.27,-41:24:41.7,Cen -NGC4931,G,13:03:00.88,+28:01:56.9,Com -NGC4932,G,13:02:37.71,+50:26:18.3,CVn -NGC4933,GTrpl,13:03:57.90,-11:29:52.0,Vir -NGC4933A,G,13:03:54.73,-11:30:18.2,Vir -NGC4933B,G,13:03:56.74,-11:29:53.0,Vir -NGC4933C,G,13:04:01.09,-11:29:26.0,Vir -NGC4934,G,13:03:16.24,+28:01:49.5,Com -NGC4935,G,13:03:21.23,+14:22:39.0,Com -NGC4936,G,13:04:16.89,-30:31:34.5,Cen -NGC4937,*Ass,13:04:51.57,-47:13:09.7,Cen -NGC4938,G,13:02:57.59,+51:19:06.7,CVn -NGC4939,G,13:04:14.39,-10:20:22.6,Vir -NGC4940,G,13:05:00.25,-47:14:12.9,Cen -NGC4941,G,13:04:13.14,-05:33:05.8,Vir -NGC4942,G,13:04:19.11,-07:38:58.0,Vir -NGC4943,G,13:03:44.94,+28:05:03.2,Com -NGC4944,G,13:03:49.95,+28:11:08.6,Com -NGC4945,G,13:05:27.48,-49:28:05.6,Cen -NGC4945A,G,13:06:33.73,-49:41:26.3,Cen -NGC4946,G,13:05:29.38,-43:35:28.1,Cen -NGC4947,G,13:05:20.20,-35:20:14.7,Cen -NGC4947A,G,13:04:20.83,-35:13:40.0,Cen -NGC4948,G,13:04:55.96,-07:56:51.7,Vir -NGC4948A,G,13:05:05.79,-08:09:40.8,Vir -NGC4949,G,13:04:17.92,+29:01:46.3,Com -NGC4950,G,13:05:36.53,-43:30:01.9,Cen -NGC4951,G,13:05:07.70,-06:29:37.8,Vir -NGC4952,G,13:04:58.38,+29:07:20.1,Com -NGC4953,G,13:06:10.47,-37:35:08.7,Cen -NGC4954,G,13:02:19.87,+75:24:15.0,Dra -NGC4955,G,13:06:04.82,-29:45:15.4,Hya -NGC4956,G,13:05:00.94,+35:10:40.9,CVn -NGC4957,G,13:05:12.39,+27:34:11.1,Com -NGC4958,G,13:05:48.88,-08:01:13.0,Vir -NGC4959,G,13:05:41.03,+33:10:44.1,CVn -NGC4960,Dup,13:05:47.57,+27:44:02.9,Com -NGC4961,G,13:05:47.57,+27:44:02.9,Com -NGC4962,Dup,13:04:58.38,+29:07:20.1,Com -NGC4963,G,13:05:51.95,+41:43:18.9,CVn -NGC4964,G,13:05:24.86,+56:19:22.0,UMa -NGC4965,G,13:07:09.38,-28:13:41.5,Hya -NGC4966,G,13:06:17.31,+29:03:46.3,Com -NGC4967,G,13:05:36.43,+53:33:51.4,UMa -NGC4968,G,13:07:05.98,-23:40:37.3,Hya -NGC4969,GPair,13:07:03.00,+13:38:13.0,Vir -NGC4969 NED01,G,13:07:02.74,+13:38:14.5,Vir -NGC4969 NED02,G,13:07:03.47,+13:38:13.6,Vir -NGC4970,G,13:07:33.74,-24:00:30.8,Hya -NGC4971,G,13:06:54.96,+28:32:52.9,Com -NGC4972,Dup,13:02:19.87,+75:24:15.0,Dra -NGC4973,G,13:05:32.15,+53:41:06.5,UMa -NGC4974,G,13:05:55.89,+53:39:33.4,UMa -NGC4975,G,13:07:50.20,-05:01:02.9,Vir -NGC4976,G,13:08:37.53,-49:30:23.1,Cen -NGC4977,G,13:06:04.43,+55:39:21.9,UMa -NGC4978,G,13:07:50.53,+18:24:55.8,Com -NGC4979,G,13:07:42.82,+24:48:38.1,Com -NGC4980,G,13:09:10.08,-28:38:30.4,Hya -NGC4981,G,13:08:48.74,-06:46:39.1,Vir -NGC4982,Other,13:08:46.14,-10:35:19.2,Vir -NGC4983,G,13:08:27.29,+28:19:13.8,Com -NGC4984,G,13:08:57.23,-15:30:58.7,Vir -NGC4985,G,13:08:12.10,+41:40:34.8,CVn -NGC4986,G,13:08:24.46,+35:12:23.2,CVn -NGC4987,G,13:07:59.06,+51:55:45.5,CVn -NGC4988,G,13:09:54.38,-43:06:20.6,Cen -NGC4989,G,13:09:16.05,-05:23:47.1,Vir -NGC4990,G,13:09:17.29,-05:16:22.1,Vir -NGC4991,G,13:09:15.11,+02:20:51.6,Vir -NGC4992,G,13:09:05.60,+11:38:03.0,Vir -NGC4993,G,13:09:47.70,-23:23:02.0,Hya -NGC4994,Dup,13:09:47.70,-23:23:02.0,Hya -NGC4995,G,13:09:40.65,-07:50:00.3,Vir -NGC4996,G,13:09:31.89,+00:51:25.3,Vir -NGC4997,G,13:09:51.70,-16:30:55.7,Vir -NGC4998,G,13:08:10.16,+50:39:49.5,CVn -NGC4999,G,13:09:33.13,+01:40:23.0,Vir -NGC5000,G,13:09:47.49,+28:54:25.0,Com -NGC5001,G,13:09:33.11,+53:29:39.4,UMa -NGC5002,G,13:10:38.01,+36:38:02.1,CVn -NGC5003,G,13:08:37.91,+43:44:15.1,CVn -NGC5004,G,13:11:01.55,+29:38:12.1,Com -NGC5004A,G,13:11:01.70,+29:34:41.8,Com -NGC5004B,G,13:10:47.65,+29:42:35.6,Com -NGC5005,G,13:10:56.23,+37:03:33.1,CVn -NGC5006,G,13:11:45.77,-19:15:42.3,Vir -NGC5007,G,13:09:14.39,+62:10:30.1,UMa -NGC5008,G,14:10:57.24,+25:29:50.0,Boo -NGC5009,G,13:10:47.03,+50:05:34.3,CVn -NGC5010,G,13:12:26.35,-15:47:52.3,Vir -NGC5011,G,13:12:51.86,-43:05:46.4,Cen -NGC5011A,G,13:12:09.72,-43:18:28.3,Cen -NGC5012,G,13:11:37.04,+22:54:55.8,Com -NGC5013,G,13:12:07.35,+03:11:56.6,Vir -NGC5014,G,13:11:31.22,+36:16:55.7,CVn -NGC5015,G,13:12:22.89,-04:20:11.3,Vir -NGC5016,G,13:12:06.68,+24:05:42.0,Com -NGC5017,G,13:12:54.50,-16:45:57.0,Vir -NGC5018,G,13:13:01.03,-19:31:05.5,Vir -NGC5019,G,13:12:42.39,+04:43:46.8,Vir -NGC5020,G,13:12:39.87,+12:35:59.1,Vir -NGC5021,G,13:12:06.27,+46:11:46.1,CVn -NGC5022,G,13:13:30.79,-19:32:47.9,Vir -NGC5023,G,13:12:12.60,+44:02:28.4,CVn -NGC5024,GCl,13:12:55.23,+18:10:08.8,Com -NGC5025,G,13:12:44.75,+31:48:33.8,CVn -NGC5026,G,13:14:13.64,-42:57:40.6,Cen -NGC5027,G,13:13:20.99,+06:03:40.6,Vir -NGC5028,G,13:13:45.86,-13:02:32.9,Vir -NGC5029,G,13:12:37.59,+47:03:47.8,CVn -NGC5030,G,13:13:54.15,-16:29:27.4,Vir -NGC5031,G,13:14:03.22,-16:07:23.2,Vir -NGC5032,G,13:13:26.95,+27:48:08.6,Com -NGC5033,G,13:13:27.47,+36:35:38.2,CVn -NGC5034,G,13:12:19.01,+70:38:57.7,UMi -NGC5035,G,13:14:49.23,-16:29:33.7,Vir -NGC5036,G,13:14:42.84,-04:10:42.9,Vir -NGC5037,G,13:14:59.37,-16:35:25.1,Vir -NGC5038,G,13:15:02.13,-15:57:06.5,Vir -NGC5039,G,13:14:51.92,-04:09:29.4,Vir -NGC5040,G,13:13:32.64,+51:15:30.1,CVn -NGC5041,G,13:14:32.46,+30:42:20.8,Com -NGC5042,G,13:15:31.02,-23:59:02.6,Hya -NGC5043,OCl,13:16:14.66,-60:02:43.8,Cen -NGC5044,G,13:15:23.97,-16:23:07.9,Vir -NGC5045,*Ass,13:16:47.00,-63:25:00.0,Cen -NGC5046,G,13:15:45.12,-16:19:36.6,Vir -NGC5047,G,13:15:48.52,-16:31:08.0,Vir -NGC5048,G,13:16:08.38,-28:24:37.9,Hya -NGC5049,G,13:15:59.30,-16:23:49.8,Vir -NGC5050,G,13:15:41.73,+02:52:44.3,Vir -NGC5051,G,13:16:20.10,-28:17:08.5,Hya -NGC5052,G,13:15:34.89,+29:40:33.9,Com -NGC5053,GCl,13:16:26.99,+17:41:51.9,Com -NGC5054,G,13:16:58.49,-16:38:05.5,Vir -NGC5055,G,13:15:49.33,+42:01:45.4,CVn -NGC5056,G,13:16:12.32,+30:57:01.2,Com -NGC5057,G,13:16:27.77,+31:01:53.4,Com -NGC5058,G,13:16:52.31,+12:32:53.6,Vir -NGC5059,G,13:16:58.46,+07:50:40.2,Vir -NGC5060,G,13:17:16.22,+06:02:14.8,Vir -NGC5061,G,13:18:05.07,-26:50:14.0,Hya -NGC5062,G,13:18:23.62,-35:27:31.2,Cen -NGC5063,G,13:18:25.71,-35:21:08.9,Cen -NGC5064,G,13:18:59.93,-47:54:31.2,Cen -NGC5065,G,13:17:30.61,+31:05:33.7,Com -NGC5066,G,13:18:28.45,-10:14:02.0,Vir -NGC5067,**,13:18:27.76,-10:08:43.0,Vir -NGC5068,G,13:18:54.81,-21:02:20.8,Vir -NGC5069,Dup,13:18:28.45,-10:14:02.0,Vir -NGC5070,G,13:19:12.56,-12:32:23.6,Vir -NGC5071,G,13:18:37.19,+07:56:07.9,Vir -NGC5072,Dup,13:19:12.56,-12:32:23.6,Vir -NGC5073,G,13:19:20.65,-14:50:40.3,Vir -NGC5074,G,13:18:25.77,+31:28:08.7,CVn -NGC5075,G,13:19:06.25,+07:49:51.7,Vir -NGC5076,G,13:19:30.40,-12:44:27.1,Vir -NGC5077,G,13:19:31.67,-12:39:25.1,Vir -NGC5078,G,13:19:49.98,-27:24:37.4,Hya -NGC5079,G,13:19:38.05,-12:41:56.5,Vir -NGC5080,G,13:19:19.22,+08:25:44.9,Vir -NGC5081,G,13:19:08.24,+28:30:24.9,Com -NGC5082,G,13:20:40.03,-43:41:59.8,Cen -NGC5083,G,13:19:03.10,+39:35:21.6,CVn -NGC5084,G,13:20:16.92,-21:49:39.3,Vir -NGC5085,G,13:20:17.75,-24:26:24.5,Hya -NGC5086,**,13:20:59.41,-43:44:00.7,Cen -NGC5087,G,13:20:24.96,-20:36:39.6,Vir -NGC5088,G,13:20:20.25,-12:34:18.4,Vir -NGC5089,G,13:19:39.36,+30:15:23.6,Com -NGC5090,G,13:21:12.82,-43:42:16.4,Cen -NGC5090A,G,13:19:21.11,-43:38:57.8,Cen -NGC5090B,G,13:20:17.52,-43:51:52.8,Cen -NGC5091,G,13:21:17.72,-43:43:10.8,Cen -NGC5092,G,13:19:51.53,+22:59:59.6,Com -NGC5093,G,13:19:37.81,+40:23:10.0,CVn -NGC5094,G,13:20:46.85,-14:04:50.4,Vir -NGC5095,G,13:20:36.87,-02:17:21.6,Vir -NGC5096,GTrpl,13:20:08.70,+33:05:22.0,CVn -NGC5096 NED01,G,13:20:08.69,+33:05:16.6,CVn -NGC5096 NED02,G,13:20:09.16,+33:05:26.7,CVn -NGC5096 NED03,G,13:20:08.09,+33:05:22.5,CVn -NGC5097,G,13:20:59.69,-12:28:16.5,Vir -NGC5098,GPair,13:20:16.20,+33:08:39.0,CVn -NGC5098 NED01,G,13:20:17.75,+33:08:41.1,CVn -NGC5098 NED02,G,13:20:14.73,+33:08:36.3,CVn -NGC5099,G,13:21:19.57,-13:02:32.5,Vir -NGC5100,GPair,13:20:58.60,+08:58:55.0,Vir -NGC5100 NED01,G,13:20:57.58,+08:59:07.7,Vir -NGC5100 NED02,Dup,13:20:59.59,+08:58:41.9,Vir -NGC5101,G,13:21:46.24,-27:25:49.9,Hya -NGC5102,G,13:21:57.61,-36:37:48.9,Cen -NGC5103,G,13:20:30.08,+43:05:02.3,CVn -NGC5104,G,13:21:23.09,+00:20:32.6,Vir -NGC5105,G,13:21:49.09,-13:12:24.4,Vir -NGC5106,G,13:20:59.59,+08:58:41.9,Vir -NGC5107,G,13:21:24.68,+38:32:15.4,CVn -NGC5108,G,13:23:18.84,-32:20:31.6,Cen -NGC5109,G,13:20:52.35,+57:38:41.1,UMa -NGC5110,G,13:22:56.52,-12:57:53.3,Vir -NGC5111,Dup,13:22:56.52,-12:57:53.3,Vir -NGC5112,G,13:21:56.40,+38:44:04.9,CVn -NGC5113,Dup,13:20:52.35,+57:38:41.1,UMa -NGC5114,G,13:24:01.73,-32:20:38.2,Cen -NGC5115,G,13:23:00.38,+13:57:02.4,Vir -NGC5116,G,13:22:55.61,+26:58:50.6,Com -NGC5117,G,13:22:56.48,+28:18:59.2,CVn -NGC5118,G,13:23:27.45,+06:23:33.2,Vir -NGC5119,G,13:24:00.32,-12:16:35.2,Vir -NGC5120,OCl,13:25:39.41,-63:27:30.0,Cen -NGC5121,G,13:24:45.61,-37:40:55.9,Cen -NGC5121A,G,13:25:32.80,-37:22:43.7,Cen -NGC5122,G,13:24:14.94,-10:39:15.4,Vir -NGC5123,G,13:23:10.52,+43:05:10.5,CVn -NGC5124,G,13:24:50.43,-30:18:27.1,Cen -NGC5125,G,13:24:00.71,+09:42:36.7,Vir -NGC5126,G,13:24:53.60,-30:20:01.0,Cen -NGC5127,G,13:23:45.02,+31:33:56.7,CVn -NGC5128,G,13:25:27.62,-43:01:08.8,Cen -NGC5129,G,13:24:10.01,+13:58:35.5,Vir -NGC5130,G,13:24:27.16,-10:12:36.8,Vir -NGC5131,G,13:23:56.94,+30:59:17.0,CVn -NGC5132,G,13:24:28.90,+14:05:33.3,Vir -NGC5133,G,13:24:52.91,-04:04:55.2,Vir -NGC5134,G,13:25:18.55,-21:08:03.0,Vir -NGC5135,G,13:25:44.06,-29:50:01.2,Hya -NGC5136,G,13:24:51.41,+13:44:16.3,Vir -NGC5137,G,13:24:52.50,+14:04:37.9,Vir -NGC5138,OCl,13:27:15.21,-59:02:27.4,Cen -NGC5139,GCl,13:26:45.89,-47:28:36.7,Cen -NGC5140,G,13:26:21.73,-33:52:06.5,Cen -NGC5141,G,13:24:51.44,+36:22:42.7,CVn -NGC5142,G,13:25:01.11,+36:23:58.2,CVn -NGC5143,G,13:25:01.28,+36:26:13.8,CVn -NGC5144,GPair,13:22:53.70,+70:30:44.0,UMi -NGC5144 NED01,G,13:22:53.49,+70:30:36.4,UMi -NGC5144 NED02,G,13:22:54.08,+70:30:53.0,UMi -NGC5145,G,13:25:13.92,+43:16:02.2,CVn -NGC5146,G,13:26:37.49,-12:19:26.3,Vir -NGC5147,G,13:26:19.73,+02:06:03.1,Vir -NGC5148,G,13:26:38.78,+02:18:49.4,Vir -NGC5149,G,13:26:09.15,+35:56:03.9,CVn -NGC5150,G,13:27:36.54,-29:33:44.1,Hya -NGC5151,G,13:26:40.84,+16:52:25.7,Com -NGC5152,G,13:27:51.12,-29:37:07.0,Hya -NGC5153,G,13:27:54.33,-29:37:04.9,Hya -NGC5154,G,13:26:28.54,+36:00:37.0,CVn -NGC5155,*Ass,13:28:35.09,-63:30:31.3,Cen -NGC5156,G,13:28:44.09,-48:55:00.5,Cen -NGC5157,G,13:27:16.85,+32:01:50.6,CVn -NGC5158,G,13:27:46.97,+17:46:43.8,Com -NGC5159,G,13:28:16.17,+02:58:57.6,Vir -NGC5160,**,13:28:21.60,+05:59:45.2,Vir -NGC5161,G,13:29:13.91,-33:10:25.8,Cen -NGC5162,G,13:29:25.93,+11:00:28.4,Vir -NGC5163,G,13:26:54.26,+52:45:12.7,UMa -NGC5164,G,13:27:11.90,+55:29:13.8,UMa -NGC5165,G,13:28:39.18,+11:23:13.4,Vir -NGC5166,G,13:28:15.04,+32:01:56.6,CVn -NGC5167,G,13:28:40.23,+12:42:40.3,Vir -NGC5168,OCl,13:31:05.25,-60:56:21.2,Cen -NGC5169,G,13:28:10.05,+46:40:19.7,CVn -NGC5170,G,13:29:48.79,-17:57:59.1,Vir -NGC5171,G,13:29:21.56,+11:44:06.4,Vir -NGC5172,G,13:29:19.31,+17:03:06.9,Com -NGC5173,G,13:28:25.27,+46:35:29.9,CVn -NGC5174,Dup,13:29:25.93,+11:00:28.4,Vir -NGC5175,*,13:29:26.27,+10:59:42.5,Vir -NGC5176,G,13:29:24.96,+11:46:53.3,Vir -NGC5177,G,13:29:24.26,+11:47:49.3,Vir -NGC5178,G,13:29:29.31,+11:37:29.2,Vir -NGC5179,G,13:29:30.88,+11:44:45.0,Vir -NGC5180,G,13:29:26.96,+16:49:33.0,Com -NGC5181,G,13:29:42.00,+13:18:14.8,Vir -NGC5182,G,13:30:41.11,-28:09:01.1,Hya -NGC5183,G,13:30:06.15,-01:43:14.1,Vir -NGC5184,G,13:30:11.49,-01:39:47.3,Vir -NGC5185,G,13:30:02.25,+13:24:57.8,Vir -NGC5186,G,13:30:03.90,+12:10:30.7,Vir -NGC5187,G,13:29:48.19,+31:07:48.5,CVn -NGC5188,G,13:31:28.28,-34:47:39.9,Cen -NGC5189,PN,13:33:32.91,-65:58:26.6,Mus -NGC5190,G,13:30:38.55,+18:08:04.6,Com -NGC5191,G,13:30:47.36,+11:12:02.7,Vir -NGC5192,G,13:30:51.68,-01:46:43.3,Vir -NGC5193,G,13:31:53.53,-33:14:03.2,Cen -NGC5193A,G,13:31:49.08,-33:14:22.1,Cen -NGC5194,G,13:29:52.71,+47:11:42.6,CVn -NGC5195,G,13:29:59.59,+47:15:58.1,CVn -NGC5196,G,13:31:19.66,-01:36:54.2,Vir -NGC5197,G,13:31:25.13,-01:41:35.0,Vir -NGC5198,G,13:30:11.40,+46:40:14.8,CVn -NGC5199,G,13:30:42.76,+34:49:50.4,CVn -NGC5200,**,13:31:42.46,-00:01:48.6,Vir -NGC5201,G,13:29:16.21,+53:04:55.1,UMa -NGC5202,G,13:32:00.51,-01:41:55.6,Vir -NGC5203,G,13:32:13.40,-08:47:10.4,Vir -NGC5204,G,13:29:36.51,+58:25:07.4,UMa -NGC5205,G,13:30:03.58,+62:30:41.7,UMa -NGC5206,G,13:33:43.98,-48:09:04.2,Cen -NGC5207,G,13:32:13.99,+13:53:32.6,Vir -NGC5208,G,13:32:27.93,+07:18:59.2,Vir -NGC5209,G,13:32:42.54,+07:19:38.1,Vir -NGC5210,G,13:32:49.25,+07:10:12.3,Vir -NGC5211,G,13:33:05.34,-01:02:08.9,Vir -NGC5212,G,13:32:56.05,+07:17:15.9,Vir -NGC5213,GPair,13:34:39.40,+04:08:00.0,Vir -NGC5214,G,13:32:48.70,+41:52:18.6,CVn -NGC5215,GPair,13:35:08.20,-33:29:02.0,Cen -NGC5215A,G,13:35:06.66,-33:28:51.6,Cen -NGC5215B,G,13:35:09.86,-33:28:58.7,Cen -NGC5216,G,13:32:06.89,+62:42:02.5,UMa -NGC5217,G,13:34:05.93,+17:51:24.7,Com -NGC5218,G,13:32:10.38,+62:46:03.9,UMa -NGC5219,Dup,13:38:41.70,-45:51:21.5,Cen -NGC5220,G,13:35:56.67,-33:27:13.7,Cen -NGC5221,G,13:34:55.90,+13:49:57.0,Vir -NGC5222,GPair,13:34:56.70,+13:44:36.0,Vir -NGC5222 NED01,G,13:34:55.95,+13:44:31.8,Vir -NGC5222 NED02,G,13:34:57.59,+13:44:39.4,Vir -NGC5223,G,13:34:25.22,+34:41:25.6,CVn -NGC5224,G,13:35:08.86,+06:28:52.0,Vir -NGC5225,G,13:33:20.16,+51:29:25.2,CVn -NGC5226,G,13:35:03.62,+13:55:19.8,Vir -NGC5227,G,13:35:24.56,+01:24:38.1,Vir -NGC5228,G,13:34:35.06,+34:46:40.1,CVn -NGC5229,G,13:34:02.83,+47:54:55.6,CVn -NGC5230,G,13:35:31.88,+13:40:34.2,Vir -NGC5231,G,13:35:48.25,+02:59:56.1,Vir -NGC5232,G,13:36:08.26,-08:29:51.8,Vir -NGC5233,G,13:35:13.34,+34:40:38.8,CVn -NGC5234,G,13:37:29.94,-49:50:12.9,Cen -NGC5235,G,13:36:01.41,+06:35:07.3,Vir -NGC5236,G,13:37:00.95,-29:51:55.5,Hya -NGC5237,G,13:37:39.05,-42:50:49.1,Cen -NGC5238,G,13:34:42.51,+51:36:49.2,CVn -NGC5239,G,13:36:26.20,+07:22:10.5,Boo -NGC5240,G,13:35:55.20,+35:35:17.7,CVn -NGC5241,G,13:36:39.87,-08:24:06.8,Vir -NGC5242,Other,13:37:07.39,+02:46:14.1,Vir -NGC5243,G,13:36:14.95,+38:20:38.1,CVn -NGC5244,G,13:38:41.70,-45:51:21.5,Cen -NGC5245,G,13:37:23.23,+03:53:50.7,Vir -NGC5246,G,13:37:29.37,+04:06:16.0,Vir -NGC5247,G,13:38:03.04,-17:53:02.5,Vir -NGC5248,G,13:37:32.02,+08:53:06.6,Boo -NGC5249,G,13:37:37.50,+15:58:20.0,Boo -NGC5250,G,13:36:07.33,+51:14:08.9,UMa -NGC5251,G,13:37:24.83,+27:25:09.2,Boo -NGC5252,G,13:38:15.96,+04:32:33.3,Vir -NGC5253,G,13:39:55.96,-31:38:24.4,Cen -NGC5254,G,13:39:37.91,-11:29:37.9,Vir -NGC5255,G,13:37:18.12,+57:06:32.2,UMa -NGC5256,GPair,13:38:17.50,+48:16:37.0,UMa -NGC5256 NED01,G,13:38:17.31,+48:16:32.0,UMa -NGC5256 NED02,G,13:38:17.79,+48:16:41.0,UMa -NGC5257,G,13:39:52.91,+00:50:24.0,Vir -NGC5258,G,13:39:57.69,+00:49:51.4,Vir -NGC5259,GPair,13:39:23.90,+30:59:29.0,CVn -NGC5259 NED01,G,13:39:23.13,+30:59:32.5,CVn -NGC5259 NED02,G,13:39:24.67,+30:59:26.9,CVn -NGC5260,G,13:40:19.90,-23:51:29.1,Hya -NGC5261,G,13:40:16.09,+05:04:34.7,Vir -NGC5262,G,13:35:38.58,+75:02:21.9,UMi -NGC5263,G,13:39:55.64,+28:24:02.1,CVn -NGC5264,G,13:41:36.68,-29:54:47.1,Hya -NGC5265,G,13:40:09.11,+36:51:39.9,CVn -NGC5266,G,13:43:02.11,-48:10:09.9,Cen -NGC5266A,G,13:40:37.10,-48:20:31.1,Cen -NGC5267,G,13:40:39.96,+38:47:38.7,CVn -NGC5268,*,13:42:12.59,-13:51:34.4,Vir -NGC5269,OCl,13:44:44.20,-62:54:45.0,Cen -NGC5270,G,13:42:10.88,+04:15:45.3,Vir -NGC5271,G,13:41:42.41,+30:07:31.6,CVn -NGC5272,GCl,13:42:11.23,+28:22:31.6,CVn -NGC5273,G,13:42:08.34,+35:39:15.2,CVn -NGC5274,G,13:42:23.31,+29:50:52.2,CVn -NGC5275,G,13:42:23.56,+29:49:29.6,CVn -NGC5276,G,13:42:22.00,+35:37:26.9,CVn -NGC5277,G,13:42:38.38,+29:57:15.9,CVn -NGC5278,G,13:41:39.62,+55:40:14.3,UMa -NGC5279,G,13:41:43.75,+55:40:25.7,UMa -NGC5280,G,13:42:55.54,+29:52:07.0,CVn -NGC5281,OCl,13:46:35.15,-62:54:59.5,Cen -NGC5282,G,13:43:24.88,+30:04:10.3,CVn -NGC5283,G,13:41:05.76,+67:40:20.3,Dra -NGC5284,*Ass,13:47:23.30,-59:08:57.8,Cen -NGC5285,G,13:44:25.79,+02:06:35.7,Vir -NGC5286,GCl,13:46:26.58,-51:22:24.5,Cen -NGC5287,G,13:44:52.54,+29:46:15.3,CVn -NGC5288,OCl,13:48:44.94,-64:41:07.4,Cir -NGC5289,G,13:45:08.71,+41:30:12.2,CVn -NGC5290,G,13:45:19.18,+41:42:45.3,CVn -NGC5291,G,13:47:24.48,-30:24:25.2,Cen -NGC5292,G,13:47:40.07,-30:56:22.3,Cen -NGC5293,G,13:46:52.68,+16:16:22.3,Boo -NGC5294,G,13:45:18.13,+55:17:27.0,UMa -NGC5295,G,13:38:39.39,+79:27:32.0,Cam -NGC5296,G,13:46:18.66,+43:51:04.9,CVn -NGC5297,G,13:46:23.67,+43:52:20.4,CVn -NGC5298,G,13:48:36.49,-30:25:42.7,Cen -NGC5299,OCl,13:50:26.26,-59:56:51.8,Cen -NGC5300,G,13:48:16.04,+03:57:03.1,Vir -NGC5301,G,13:46:24.67,+46:06:25.4,CVn -NGC5302,G,13:48:49.68,-30:30:40.0,Cen -NGC5303,G,13:47:44.99,+38:18:16.7,CVn -NGC5304,G,13:50:01.48,-30:34:42.5,Cen -NGC5305,G,13:47:55.74,+37:49:34.8,CVn -NGC5306,G,13:49:11.40,-07:13:27.8,Vir -NGC5307,PN,13:51:03.30,-51:12:20.8,Cen -NGC5308,G,13:47:00.43,+60:58:23.4,UMa -NGC5309,G,13:50:10.76,-15:37:06.3,Vir -NGC5310,*,13:49:47.74,+00:04:09.1,Vir -NGC5311,G,13:48:56.08,+39:59:06.4,CVn -NGC5312,G,13:49:50.57,+33:37:18.3,CVn -NGC5313,G,13:49:44.34,+39:59:05.2,CVn -NGC5314,G,13:46:11.40,+70:20:22.4,UMi -NGC5315,PN,13:53:56.89,-66:30:51.0,Cir -NGC5316,OCl,13:53:57.22,-61:52:08.8,Cen -NGC5317,Dup,13:56:12.00,+05:00:52.1,Vir -NGC5318,G,13:50:36.01,+33:42:17.6,CVn -NGC5319,G,13:50:40.66,+33:45:41.6,CVn -NGC5320,G,13:50:20.38,+41:21:58.4,CVn -NGC5321,G,13:50:43.69,+33:37:57.4,CVn -NGC5322,G,13:49:15.27,+60:11:25.9,UMa -NGC5323,G,13:45:36.52,+76:49:42.0,UMi -NGC5324,G,13:52:05.89,-06:03:29.9,Vir -NGC5325,G,13:50:54.21,+38:16:29.2,CVn -NGC5326,G,13:50:50.70,+39:34:29.5,CVn -NGC5327,G,13:52:04.17,-02:12:23.8,Vir -NGC5328,G,13:52:53.31,-28:29:21.8,Hya -NGC5329,G,13:52:10.09,+02:19:30.4,Vir -NGC5330,G,13:52:59.19,-28:28:14.4,Hya -NGC5331,GPair,13:52:16.30,+02:06:10.9,Vir -NGC5331 NED01,G,13:52:16.15,+02:06:03.3,Vir -NGC5331 NED02,G,13:52:16.42,+02:06:31.1,Vir -NGC5332,G,13:52:07.92,+16:58:11.1,Boo -NGC5333,G,13:54:24.22,-48:30:45.1,Cen -NGC5334,G,13:52:54.46,-01:06:52.7,Vir -NGC5335,G,13:52:56.56,+02:48:51.4,Vir -NGC5336,G,13:52:09.80,+43:14:34.5,CVn -NGC5337,G,13:52:23.03,+39:41:14.1,CVn -NGC5338,G,13:53:26.55,+05:12:28.0,Vir -NGC5339,G,13:54:00.27,-07:55:50.4,Vir -NGC5340,G,13:48:59.91,+72:39:13.8,UMi -NGC5341,G,13:52:31.96,+37:49:02.7,CVn -NGC5342,G,13:51:25.85,+59:51:50.2,UMa -NGC5343,G,13:54:11.71,-07:35:17.3,Vir -NGC5344,GPair,13:49:58.03,+73:57:10.4,UMi -NGC5345,G,13:54:14.25,-01:26:11.3,Vir -NGC5346,G,13:53:01.89,+39:34:50.8,CVn -NGC5347,G,13:53:17.83,+33:29:27.0,CVn -NGC5348,G,13:54:11.27,+05:13:38.8,Vir -NGC5349,G,13:53:13.13,+37:52:59.3,CVn -NGC5350,G,13:53:21.63,+40:21:50.2,CVn -NGC5351,G,13:53:27.71,+37:54:53.9,CVn -NGC5352,G,13:53:38.43,+36:08:02.6,CVn -NGC5353,G,13:53:26.69,+40:16:58.9,CVn -NGC5354,G,13:53:26.70,+40:18:09.9,CVn -NGC5355,G,13:53:45.56,+40:20:19.2,CVn -NGC5356,G,13:54:58.46,+05:20:01.4,Vir -NGC5357,G,13:55:59.55,-30:20:29.2,Cen -NGC5358,G,13:54:00.41,+40:16:38.3,CVn -NGC5359,OCl,14:00:09.57,-70:23:32.3,Cir -NGC5360,G,13:55:38.75,+04:59:06.2,Vir -NGC5361,G,13:54:35.23,+38:26:57.9,CVn -NGC5362,G,13:54:53.30,+41:18:48.7,CVn -NGC5363,G,13:56:07.21,+05:15:17.2,Vir -NGC5364,G,13:56:12.00,+05:00:52.1,Vir -NGC5365,G,13:57:50.64,-43:55:52.7,Cen -NGC5365A,G,13:56:39.53,-44:00:33.3,Cen -NGC5365B,G,13:58:39.57,-43:57:49.4,Cen -NGC5366,G,13:56:24.86,-00:14:51.2,Vir -NGC5367,RfN,13:57:43.87,-39:58:42.3,Cen -NGC5368,G,13:54:29.16,+54:19:50.4,UMa -NGC5369,G,13:56:37.62,-05:28:11.6,Vir -NGC5370,G,13:54:09.37,+60:40:41.0,UMa -NGC5371,G,13:55:39.94,+40:27:42.3,CVn -NGC5372,G,13:54:45.99,+58:40:00.7,UMa -NGC5373,G,13:57:07.46,+05:15:06.8,Vir -NGC5374,G,13:57:29.63,+06:05:49.2,Vir -NGC5375,G,13:56:56.00,+29:09:51.7,CVn -NGC5376,G,13:55:16.07,+59:30:23.8,UMa -NGC5377,G,13:56:16.67,+47:14:08.5,CVn -NGC5378,G,13:56:51.02,+37:47:50.1,CVn -NGC5379,G,13:55:34.32,+59:44:34.1,UMa -NGC5380,G,13:56:56.69,+37:36:37.4,CVn -NGC5381,OCl,14:00:41.96,-59:35:12.5,Cen -NGC5382,G,13:58:14.89,+06:15:31.3,Vir -NGC5383,G,13:57:04.97,+41:50:46.5,CVn -NGC5384,G,13:58:12.86,+06:31:05.5,Vir -NGC5385,OCl,13:52:31.92,+76:09:46.0,UMi -NGC5386,G,13:58:22.34,+06:20:20.8,Vir -NGC5387,G,13:58:24.79,+06:04:16.6,Vir -NGC5388,Other,13:58:57.97,-14:09:03.2,Vir -NGC5389,G,13:56:06.33,+59:44:31.4,UMa -NGC5390,Dup,13:55:39.94,+40:27:42.3,CVn -NGC5391,G,13:57:42.78,+46:28:10.5,CVn -NGC5392,G,13:59:24.77,-03:12:33.1,Vir -NGC5393,G,14:00:32.04,-28:52:28.5,Hya -NGC5394,G,13:58:33.65,+37:27:12.5,CVn -NGC5395,G,13:58:37.98,+37:25:28.1,CVn -NGC5396,Dup,13:56:56.00,+29:09:51.7,CVn -NGC5397,G,14:01:10.46,-33:56:45.0,Cen -NGC5398,G,14:01:21.56,-33:03:49.6,Cen -NGC5399,G,13:59:31.42,+34:46:24.9,CVn -NGC5400,G,14:00:37.20,-02:51:28.1,Vir -NGC5401,G,13:59:43.43,+36:14:17.2,CVn -NGC5402,G,13:58:16.55,+59:48:52.4,UMa -NGC5403,G,13:59:50.92,+38:10:57.1,CVn -NGC5404,**,14:01:07.48,+00:05:09.2,Vir -NGC5405,G,14:01:09.47,+07:42:08.2,Boo -NGC5406,G,14:00:20.12,+38:54:55.5,CVn -NGC5407,G,14:00:50.10,+39:09:22.4,CVn -NGC5408,G,14:03:20.91,-41:22:39.7,Cen -NGC5409,G,14:01:46.08,+09:29:25.0,Boo -NGC5410,G,14:00:54.61,+40:59:19.0,CVn -NGC5411,G,14:01:59.26,+08:56:15.7,Boo -NGC5412,G,13:57:13.53,+73:37:00.2,UMi -NGC5413,G,13:57:53.53,+64:54:39.5,Dra -NGC5414,G,14:02:03.52,+09:55:45.5,Boo -NGC5415,G,13:56:56.92,+70:45:15.8,UMi -NGC5416,G,14:02:11.32,+09:26:24.4,Boo -NGC5417,G,14:02:13.04,+08:02:13.6,Boo -NGC5418,G,14:02:17.59,+07:41:03.0,Boo -NGC5419,G,14:03:38.73,-33:58:41.7,Cen -NGC5420,G,14:03:59.90,-14:37:00.7,Vir -NGC5421,GPair,14:01:41.88,+33:49:25.6,CVn -NGC5421 NED01,G,14:01:41.39,+33:49:36.6,CVn -NGC5421 NED02,G,14:01:42.11,+33:49:17.4,CVn -NGC5422,G,14:00:42.04,+55:09:52.1,UMa -NGC5423,G,14:02:48.62,+09:20:29.0,Boo -NGC5424,G,14:02:55.70,+09:25:14.4,Boo -NGC5425,G,14:00:47.67,+48:26:37.9,UMa -NGC5426,G,14:03:24.85,-06:04:08.8,Vir -NGC5427,G,14:03:26.05,-06:01:50.9,Vir -NGC5428,**,14:03:28.03,-05:59:04.1,Vir -NGC5429,**,14:03:33.36,-06:02:17.8,Vir -NGC5430,G,14:00:45.74,+59:19:41.8,UMa -NGC5431,G,14:03:07.15,+09:21:47.0,Boo -NGC5432,Other,14:03:40.65,-05:58:31.6,Vir -NGC5433,G,14:02:36.08,+32:30:37.6,CVn -NGC5434,G,14:03:23.14,+09:26:53.1,Boo -NGC5435,**,14:04:00.05,-05:55:54.0,Vir -NGC5436,G,14:03:41.10,+09:34:24.6,Boo -NGC5437,G,14:03:47.34,+09:31:25.3,Boo -NGC5438,G,14:03:48.00,+09:36:38.1,Boo -NGC5439,G,14:01:57.67,+46:18:42.9,CVn -NGC5440,G,14:03:01.04,+34:45:28.3,CVn -NGC5441,G,14:03:12.00,+34:41:04.6,CVn -NGC5442,G,14:04:43.22,-09:42:49.2,Vir -NGC5443,G,14:02:11.80,+55:48:50.5,UMa -NGC5444,G,14:03:24.13,+35:07:55.6,CVn -NGC5445,G,14:03:31.50,+35:01:31.1,CVn -NGC5446,Dup,14:03:48.00,+09:36:38.1,Boo -NGC5447,HII,14:02:28.40,+54:16:36.9,UMa -NGC5448,G,14:02:50.03,+49:10:21.7,UMa -NGC5449,Other,14:02:27.27,+54:19:48.8,UMa -NGC5450,HII,14:02:29.79,+54:16:15.7,UMa -NGC5451,HII,14:02:37.05,+54:21:45.1,UMa -NGC5452,G,13:54:24.90,+78:13:14.0,UMi -NGC5453,HII,14:02:56.39,+54:18:28.7,UMa -NGC5454,G,14:04:45.71,+14:22:55.4,Boo -NGC5455,HII,14:03:01.17,+54:14:29.3,UMa -NGC5456,G,14:04:58.89,+11:52:17.9,Boo -NGC5457,G,14:03:12.54,+54:20:56.2,UMa -NGC5458,HII,14:03:12.47,+54:17:54.0,UMa -NGC5459,G,14:05:00.19,+13:07:54.8,Boo -NGC5460,OCl,14:07:27.81,-48:20:33.1,Cen -NGC5461,HII,14:03:41.41,+54:19:04.9,UMa -NGC5462,HII,14:03:53.00,+54:21:56.1,UMa -NGC5463,G,14:06:10.54,+09:21:12.4,Boo -NGC5464,G,14:07:04.40,-30:01:01.6,Hya -NGC5465,*,14:06:27.28,-05:30:22.6,Vir -NGC5466,GCl,14:05:27.36,+28:32:04.2,Boo -NGC5467,*,14:06:29.50,-05:28:53.3,Vir -NGC5468,G,14:06:34.89,-05:27:11.2,Vir -NGC5469,G,14:12:29.90,+08:38:52.6,Boo -NGC5470,G,14:06:31.91,+06:01:46.9,Vir -NGC5471,HII,14:04:29.02,+54:23:48.9,UMa -NGC5472,G,14:06:55.02,-05:27:37.7,Vir -NGC5473,G,14:04:43.23,+54:53:33.5,UMa -NGC5474,G,14:05:01.61,+53:39:44.0,UMa -NGC5475,G,14:05:12.41,+55:44:30.7,UMa -NGC5476,G,14:08:08.49,-06:05:31.4,Vir -NGC5477,G,14:05:33.30,+54:27:40.1,UMa -NGC5478,G,14:08:08.51,-01:42:08.3,Vir -NGC5479,G,14:05:57.37,+65:41:26.7,UMi -NGC5480,G,14:06:21.58,+50:43:30.4,UMa -NGC5481,G,14:06:41.27,+50:43:24.0,Boo -NGC5482,G,14:08:30.70,+08:55:54.9,Boo -NGC5483,G,14:10:25.03,-43:19:28.6,Cen -NGC5484,G,14:06:48.22,+55:01:47.7,UMa -NGC5485,G,14:07:11.35,+55:00:06.1,UMa -NGC5486,G,14:07:24.96,+55:06:11.1,UMa -NGC5487,G,14:09:43.90,+08:04:08.9,Boo -NGC5488,G,14:08:03.11,-33:18:53.7,Cen -NGC5489,G,14:12:00.72,-46:05:19.4,Cen -NGC5490,G,14:09:57.29,+17:32:44.0,Boo -NGC5490C,G,14:10:06.91,+17:36:56.7,Boo -NGC5491,G,14:10:57.35,+06:21:53.5,Vir -NGC5492,G,14:10:35.20,+19:36:44.4,Boo -NGC5493,G,14:11:29.38,-05:02:37.0,Vir -NGC5494,G,14:12:24.18,-30:38:38.7,Cen -NGC5495,G,14:12:23.35,-27:06:28.9,Hya -NGC5496,G,14:11:37.86,-01:09:32.8,Vir -NGC5497,G,14:10:31.64,+38:53:36.8,Boo -NGC5498,G,14:11:04.53,+25:41:52.7,Boo -NGC5499,G,14:10:47.65,+35:54:48.6,Boo -NGC5500,G,14:10:15.23,+48:32:46.0,Boo -NGC5501,G,14:12:20.18,+01:16:21.1,Vir -NGC5502,G,14:09:33.95,+60:24:34.5,UMa -NGC5503,Dup,14:09:33.95,+60:24:34.5,UMa -NGC5504,G,14:12:15.81,+15:50:30.9,Boo -NGC5504B,G,14:12:12.72,+15:52:08.0,Boo -NGC5504C,G,14:12:15.79,+15:52:45.6,Boo -NGC5505,G,14:12:31.71,+13:18:17.0,Boo -NGC5506,G,14:13:14.89,-03:12:27.3,Vir -NGC5507,G,14:13:19.87,-03:08:56.0,Vir -NGC5508,G,14:12:29.04,+24:38:07.8,Boo -NGC5509,G,14:12:39.58,+20:23:13.2,Boo -NGC5510,G,14:13:37.22,-17:59:01.7,Vir -NGC5511,G,14:13:05.42,+08:37:54.8,Boo -NGC5512,G,14:12:41.13,+30:51:18.2,Boo -NGC5513,G,14:13:08.63,+20:24:58.6,Boo -NGC5514,GPair,14:13:38.78,+07:39:34.3,Boo -NGC5514 NED01,G,14:13:38.44,+07:39:37.9,Boo -NGC5514 NED02,G,14:13:39.38,+07:39:28.8,Boo -NGC5515,G,14:12:38.17,+39:18:37.0,Boo -NGC5516,G,14:15:54.69,-48:06:53.5,Cen -NGC5517,G,14:12:51.24,+35:42:39.2,Boo -NGC5518,G,14:13:47.68,+20:50:53.9,Boo -NGC5519,G,14:14:20.87,+07:30:57.3,Boo -NGC5520,G,14:12:22.79,+50:20:54.3,Boo -NGC5521,G,14:15:23.72,+04:24:30.7,Vir -NGC5522,G,14:14:50.37,+15:08:48.8,Boo -NGC5523,G,14:14:52.32,+25:19:03.4,Boo -NGC5524,*,14:14:00.64,+36:25:02.5,Boo -NGC5525,G,14:15:39.23,+14:16:57.4,Boo -NGC5526,G,14:13:53.76,+57:46:16.8,UMa -NGC5527,G,14:14:27.23,+36:24:16.2,Boo -NGC5528,G,14:16:19.92,+08:17:36.5,Boo -NGC5529,G,14:15:34.07,+36:13:35.7,Boo -NGC5530,G,14:18:27.15,-43:23:18.9,Lup -NGC5531,GPair,14:16:43.30,+10:53:05.2,Boo -NGC5532,G,14:16:52.95,+10:48:26.6,Boo -NGC5533,G,14:16:07.74,+35:20:37.8,Boo -NGC5534,G,14:17:40.26,-07:25:02.7,Vir -NGC5535,G,14:17:31.27,+08:12:30.2,Boo -NGC5536,G,14:16:23.86,+39:30:07.9,Boo -NGC5537,G,14:17:37.09,+07:03:18.0,Vir -NGC5538,G,14:17:42.49,+07:28:36.0,Boo -NGC5539,G,14:17:37.77,+08:10:46.6,Boo -NGC5540,G,14:14:54.43,+60:00:40.0,UMa -NGC5541,G,14:16:31.80,+39:35:20.6,Boo -NGC5542,G,14:17:53.20,+07:33:31.6,Boo -NGC5543,G,14:18:04.07,+07:39:17.5,Boo -NGC5544,G,14:17:02.52,+36:34:17.7,Boo -NGC5545,G,14:17:05.16,+36:34:30.4,Boo -NGC5546,G,14:18:09.23,+07:33:52.4,Boo -NGC5547,G,14:09:45.20,+78:36:03.8,UMi -NGC5548,G,14:17:59.53,+25:08:12.4,Boo -NGC5549,G,14:18:38.81,+07:22:38.4,Vir -NGC5550,G,14:18:27.97,+12:52:59.1,Boo -NGC5551,G,14:18:54.84,+05:27:05.3,Vir -NGC5552,G,14:19:03.85,+07:01:54.1,Vir -NGC5553,G,14:18:29.73,+26:17:16.2,Boo -NGC5554,G,14:19:15.00,+07:01:16.1,Vir -NGC5555,G,14:18:48.08,-19:08:20.1,Vir -NGC5556,G,14:20:34.09,-29:14:30.4,Hya -NGC5557,G,14:18:25.72,+36:29:36.8,Boo -NGC5558,Dup,14:19:03.85,+07:01:54.1,Vir -NGC5559,G,14:19:12.78,+24:47:55.4,Boo -NGC5560,G,14:20:04.49,+03:59:32.9,Vir -NGC5561,G,14:17:22.83,+58:45:01.8,UMa -NGC5562,G,14:20:11.05,+10:15:46.2,Boo -NGC5563,G,14:20:13.09,+07:03:19.5,Vir -NGC5564,Dup,14:19:15.00,+07:01:16.1,Vir -NGC5565,*,14:19:18.50,+06:59:41.8,Vir -NGC5566,G,14:20:19.89,+03:56:01.5,Vir -NGC5567,G,14:19:17.64,+35:08:16.6,Boo -NGC5568,G,14:19:21.32,+35:05:31.8,Boo -NGC5569,G,14:20:32.09,+03:58:59.6,Vir -NGC5570,Dup,14:14:20.87,+07:30:57.3,Boo -NGC5571,Other,14:19:32.03,+35:09:03.4,Boo -NGC5572,G,14:19:35.30,+36:08:26.4,Boo -NGC5573,G,14:20:41.51,+06:54:26.3,Vir -NGC5574,G,14:20:55.97,+03:14:16.8,Vir -NGC5575,G,14:20:59.41,+06:12:09.6,Vir -NGC5576,G,14:21:03.68,+03:16:15.6,Vir -NGC5577,G,14:21:13.11,+03:26:08.8,Vir -NGC5578,Dup,14:20:59.41,+06:12:09.6,Vir -NGC5579,G,14:20:26.48,+35:11:19.7,Boo -NGC5580,G,14:21:38.41,+35:12:17.5,Boo -NGC5581,G,14:21:16.31,+23:28:48.1,Boo -NGC5582,G,14:20:43.12,+39:41:36.9,Boo -NGC5583,G,14:21:40.50,+13:13:56.6,Boo -NGC5584,G,14:22:23.77,-00:23:15.6,Vir -NGC5585,G,14:19:48.20,+56:43:44.6,UMa -NGC5586,Other,14:22:07.66,+13:11:03.4,Boo -NGC5587,G,14:22:10.71,+13:55:05.0,Boo -NGC5588,G,14:21:25.11,+35:16:14.3,Boo -NGC5589,Dup,14:21:25.11,+35:16:14.3,Boo -NGC5590,Dup,14:21:38.41,+35:12:17.5,Boo -NGC5591,GPair,14:22:34.00,+13:43:00.0,Boo -NGC5591 NED01,G,14:22:33.29,+13:43:01.9,Boo -NGC5591 NED02,G,14:22:34.76,+13:43:02.5,Boo -NGC5592,G,14:23:55.05,-28:41:17.0,Hya -NGC5593,OCl,14:25:39.18,-54:47:55.3,Lup -NGC5594,G,14:23:10.31,+26:15:56.9,Boo -NGC5595,G,14:24:13.24,-16:43:22.8,Lib -NGC5596,G,14:22:28.74,+37:07:19.9,Boo -NGC5597,G,14:24:27.44,-16:45:45.9,Lib -NGC5598,G,14:22:28.28,+40:19:11.3,Boo -NGC5599,G,14:23:50.61,+06:34:34.0,Vir -NGC5600,G,14:23:49.52,+14:38:19.6,Boo -NGC5601,G,14:22:53.28,+40:18:34.5,Boo -NGC5602,G,14:22:18.83,+50:30:05.0,Boo -NGC5603,G,14:23:01.53,+40:22:38.7,Boo -NGC5604,G,14:24:42.80,-03:12:43.8,Vir -NGC5605,G,14:25:07.57,-13:09:46.8,Lib -NGC5606,OCl,14:27:47.28,-59:37:56.1,Cen -NGC5607,G,14:19:26.70,+71:35:17.6,UMi -NGC5608,G,14:23:17.89,+41:46:33.2,Boo -NGC5609,G,14:23:48.30,+34:50:34.0,Boo -NGC5610,G,14:24:22.94,+24:36:50.9,Boo -NGC5611,G,14:24:04.78,+33:02:50.6,Boo -NGC5612,G,14:34:01.25,-78:23:15.0,Aps -NGC5613,G,14:24:05.96,+34:53:31.6,Boo -NGC5614,G,14:24:07.59,+34:51:31.9,Boo -NGC5615,G,14:24:06.50,+34:51:54.0,Boo -NGC5616,G,14:24:20.70,+36:27:41.1,Boo -NGC5617,OCl,14:29:44.07,-60:42:39.0,Cen -NGC5618,G,14:27:11.81,-02:15:44.7,Vir -NGC5619,G,14:27:18.23,+04:48:10.2,Vir -NGC5619B,G,14:27:32.37,+04:49:17.8,Vir -NGC5620,Dup,14:19:26.70,+71:35:17.6,UMi -NGC5621,Other,14:27:49.81,+08:14:24.9,Boo -NGC5622,G,14:26:12.19,+48:33:50.4,Boo -NGC5623,G,14:27:08.68,+33:15:09.2,Boo -NGC5624,G,14:26:35.21,+51:35:07.3,Boo -NGC5625,GPair,14:27:01.50,+39:57:25.0,Boo -NGC5625 NED01,G,14:27:00.60,+39:57:22.4,Boo -NGC5625 NED02,G,14:27:02.33,+39:57:26.2,Boo -NGC5626,G,14:29:49.09,-29:44:54.5,Hya -NGC5627,G,14:28:34.28,+11:22:41.7,Boo -NGC5628,G,14:28:25.79,+17:55:27.7,Boo -NGC5629,G,14:28:16.37,+25:50:55.6,Boo -NGC5630,G,14:27:36.61,+41:15:27.9,Boo -NGC5631,G,14:26:33.30,+56:34:57.5,UMa -NGC5632,Dup,14:37:53.34,-00:23:56.0,Vir -NGC5633,G,14:27:28.38,+46:08:47.5,Boo -NGC5634,GCl,14:29:37.28,-05:58:35.1,Vir -NGC5635,G,14:28:31.76,+27:24:32.2,Boo -NGC5636,G,14:29:39.02,+03:15:58.7,Vir -NGC5637,G,14:28:59.60,+23:11:29.4,Boo -NGC5638,G,14:29:40.38,+03:13:59.9,Vir -NGC5639,G,14:28:46.55,+30:24:46.6,Boo -NGC5640,G,14:20:40.81,+80:07:23.2,Cam -NGC5641,G,14:29:16.62,+28:49:18.8,Boo -NGC5642,G,14:29:13.49,+30:01:35.2,Boo -NGC5643,G,14:32:40.74,-44:10:27.9,Lup -NGC5644,G,14:30:25.58,+11:55:40.8,Boo -NGC5645,G,14:30:39.35,+07:16:30.3,Vir -NGC5646,G,14:29:34.01,+35:27:43.0,Boo -NGC5647,G,14:30:36.10,+11:52:36.1,Boo -NGC5648,G,14:30:32.60,+14:01:23.0,Boo -NGC5649,Dup,14:30:32.60,+14:01:23.0,Boo -NGC5650,G,14:31:01.08,+05:58:42.3,Vir -NGC5651,Dup,14:40:11.51,-00:17:20.3,Vir -NGC5652,Dup,14:31:01.08,+05:58:42.3,Vir -NGC5653,G,14:30:10.42,+31:12:55.8,Boo -NGC5654,G,14:30:01.33,+36:21:39.3,Boo -NGC5655,G,14:30:50.93,+13:58:07.5,Boo -NGC5656,G,14:30:25.51,+35:19:15.7,Boo -NGC5657,G,14:30:43.60,+29:10:51.0,Boo -NGC5658,Dup,14:40:56.36,-00:19:05.6,Vir -NGC5659,G,14:31:06.14,+25:21:18.4,Boo -NGC5660,G,14:29:49.81,+49:37:21.6,Boo -NGC5661,G,14:31:57.39,+06:15:01.5,Vir -NGC5662,OCl,14:35:37.58,-56:37:05.1,Cen -NGC5663,G,14:33:56.30,-16:34:51.8,Lib -NGC5664,G,14:33:43.62,-14:37:10.9,Lib -NGC5665,G,14:32:25.74,+08:04:43.1,Boo -NGC5665A,Other,14:32:27.34,+08:04:43.4,Boo -NGC5666,G,14:33:09.21,+10:30:38.9,Boo -NGC5667,G,14:30:22.91,+59:28:11.2,Dra -NGC5668,G,14:33:24.34,+04:27:01.6,Vir -NGC5669,G,14:32:43.49,+09:53:25.6,Boo -NGC5670,G,14:35:36.03,-45:58:01.1,Lup -NGC5671,G,14:27:42.00,+69:41:39.0,UMi -NGC5672,G,14:32:38.34,+31:40:12.6,Boo -NGC5673,G,14:31:30.91,+49:57:31.4,Boo -NGC5674,G,14:33:52.24,+05:27:29.6,Vir -NGC5675,G,14:32:39.83,+36:18:07.9,Boo -NGC5676,G,14:32:46.85,+49:27:28.4,Boo -NGC5677,G,14:34:12.74,+25:28:04.9,Boo -NGC5678,G,14:32:05.61,+57:55:17.2,Dra -NGC5679,GTrpl,14:35:08.70,+05:21:23.0,Vir -NGC5679A,G,14:35:06.39,+05:21:24.6,Vir -NGC5679B,G,14:35:08.76,+05:21:32.2,Vir -NGC5679C,G,14:35:10.99,+05:21:15.4,Vir -NGC5680,G,14:35:44.53,-00:00:48.1,Vir -NGC5681,G,14:35:42.87,+08:18:02.2,Boo -NGC5682,G,14:34:44.98,+48:40:12.9,Boo -NGC5683,G,14:34:52.45,+48:39:42.9,Boo -NGC5684,G,14:35:50.16,+36:32:35.6,Boo -NGC5685,G,14:36:15.37,+29:54:30.2,Boo -NGC5686,G,14:36:02.59,+36:30:11.5,Boo -NGC5687,G,14:34:52.40,+54:28:33.1,Boo -NGC5688,G,14:39:35.15,-45:01:08.4,Lup -NGC5689,G,14:35:29.69,+48:44:29.9,Boo -NGC5690,G,14:37:41.07,+02:17:27.0,Vir -NGC5691,G,14:37:53.34,-00:23:56.0,Vir -NGC5692,G,14:38:18.11,+03:24:37.2,Vir -NGC5693,G,14:36:11.19,+48:35:06.1,Boo -NGC5694,GCl,14:39:36.51,-26:32:18.0,Hya -NGC5695,G,14:37:22.12,+36:34:04.1,Boo -NGC5696,G,14:36:57.08,+41:49:41.2,Boo -NGC5697,G,14:36:32.06,+41:41:09.2,Boo -NGC5698,G,14:37:14.70,+38:27:15.4,Boo -NGC5699,G,14:38:42.36,+30:27:57.3,Boo -NGC5700,G,14:37:01.56,+48:32:41.4,Boo -NGC5701,G,14:39:11.08,+05:21:48.5,Vir -NGC5702,G,14:38:55.08,+20:30:24.2,Boo -NGC5703,G,14:38:50.02,+30:26:33.1,Boo -NGC5704,G,14:38:16.28,+40:27:24.3,Boo -NGC5705,G,14:39:49.69,-00:43:06.5,Vir -NGC5706,Dup,14:38:42.36,+30:27:57.3,Boo -NGC5707,G,14:37:30.77,+51:33:42.7,Boo -NGC5708,Dup,14:38:16.28,+40:27:24.3,Boo -NGC5709,Dup,14:38:50.02,+30:26:33.1,Boo -NGC5710,G,14:39:16.19,+20:02:37.3,Boo -NGC5711,G,14:39:22.56,+19:59:27.2,Boo -NGC5712,G,14:29:41.72,+78:51:51.3,UMi -NGC5713,G,14:40:11.51,-00:17:20.3,Vir -NGC5714,G,14:38:11.52,+46:38:17.7,Boo -NGC5715,OCl,14:43:29.69,-57:34:37.3,Cir -NGC5716,G,14:41:05.52,-17:28:37.1,Lib -NGC5717,G,14:38:37.69,+46:39:47.3,Boo -NGC5718,G,14:40:42.84,+03:27:55.5,Vir -NGC5719,G,14:40:56.36,-00:19:05.6,Vir -NGC5720,G,14:38:33.29,+50:48:54.8,Boo -NGC5721,G,14:38:52.96,+46:40:28.0,Boo -NGC5722,G,14:38:54.41,+46:39:56.2,Boo -NGC5723,G,14:38:57.92,+46:41:22.6,Boo -NGC5724,*,14:39:02.13,+46:41:31.3,Boo -NGC5725,G,14:40:58.32,+02:11:11.6,Vir -NGC5726,G,14:42:56.05,-18:26:41.6,Lib -NGC5727,G,14:40:26.12,+33:59:20.8,Boo -NGC5728,G,14:42:23.90,-17:15:11.1,Lib -NGC5729,G,14:42:06.88,-09:00:34.0,Lib -NGC5730,G,14:39:52.16,+42:44:32.5,Boo -NGC5731,G,14:40:09.21,+42:46:46.4,Boo -NGC5732,G,14:40:38.95,+38:38:16.1,Boo -NGC5733,G,14:42:45.93,-00:21:03.9,Vir -NGC5734,G,14:45:09.05,-20:52:13.7,Lib -NGC5735,G,14:42:33.24,+28:43:35.2,Boo -NGC5736,G,14:43:30.81,+11:12:09.7,Boo -NGC5737,G,14:43:11.82,+18:52:47.8,Boo -NGC5738,G,14:43:56.37,+01:36:15.0,Vir -NGC5739,G,14:42:28.91,+41:50:32.4,Boo -NGC5740,G,14:44:24.45,+01:40:47.2,Vir -NGC5741,G,14:45:51.72,-11:54:51.4,Lib -NGC5742,G,14:45:36.88,-11:48:34.7,Lib -NGC5743,G,14:45:10.99,-20:54:48.6,Lib -NGC5744,G,14:46:38.65,-18:30:47.8,Lib -NGC5745,GTrpl,14:45:02.16,-13:56:46.4,Lib -NGC5746,G,14:44:55.92,+01:57:18.0,Vir -NGC5747,GPair,14:44:20.80,+12:07:47.0,Boo -NGC5747 NED01,G,14:44:20.80,+12:07:42.0,Boo -NGC5747 NED02,G,14:44:20.80,+12:07:55.2,Boo -NGC5748,G,14:45:05.11,+21:54:58.4,Boo -NGC5749,OCl,14:48:53.94,-54:29:51.7,Lup -NGC5750,G,14:46:11.12,-00:13:22.6,Vir -NGC5751,G,14:43:49.18,+53:24:02.4,Boo -NGC5752,G,14:45:14.16,+38:43:43.9,Boo -NGC5753,G,14:45:18.88,+38:48:21.2,Boo -NGC5754,G,14:45:19.65,+38:43:52.4,Boo -NGC5755,G,14:45:24.52,+38:46:47.6,Boo -NGC5756,G,14:47:33.73,-14:51:12.9,Lib -NGC5757,G,14:47:46.38,-19:04:42.8,Lib -NGC5758,G,14:47:02.08,+13:40:06.2,Boo -NGC5759,GPair,14:47:14.61,+13:27:28.6,Boo -NGC5760,G,14:47:42.26,+18:30:07.3,Boo -NGC5761,G,14:49:08.48,-20:22:33.7,Lib -NGC5762,G,14:48:42.57,+12:27:26.0,Boo -NGC5763,G,14:48:58.72,+12:29:24.5,Boo -NGC5764,OCl,14:53:32.24,-52:40:13.9,Lup -NGC5765,GPair,14:50:51.00,+05:07:01.0,Vir -NGC5765A,G,14:50:50.70,+05:07:10.0,Vir -NGC5765B,G,14:50:51.51,+05:06:52.1,Vir -NGC5766,G,14:53:09.56,-21:23:38.8,Lib -NGC5767,G,14:49:34.43,+47:22:34.2,Boo -NGC5768,G,14:52:07.95,-02:31:47.1,Lib -NGC5769,G,14:52:41.53,+07:55:54.5,Boo -NGC5770,G,14:53:15.02,+03:57:35.0,Vir -NGC5771,G,14:52:14.33,+29:50:43.5,Boo -NGC5772,G,14:51:38.88,+40:35:57.0,Boo -NGC5773,G,14:52:30.42,+29:48:26.6,Boo -NGC5774,G,14:53:42.46,+03:34:57.0,Vir -NGC5775,G,14:53:57.60,+03:32:40.0,Vir -NGC5776,G,14:54:32.75,+02:57:59.2,Vir -NGC5777,G,14:51:17.85,+58:58:40.6,Dra -NGC5778,G,14:54:31.49,+18:38:32.3,Boo -NGC5779,G,14:52:09.56,+55:53:58.0,Dra -NGC5780,G,14:54:22.67,+28:56:22.8,Boo -NGC5781,G,14:56:41.22,-17:14:38.2,Lib -NGC5782,G,14:55:55.27,+11:51:41.5,Boo -NGC5783,G,14:53:28.31,+52:04:34.0,Boo -NGC5784,G,14:54:16.45,+42:33:28.5,Boo -NGC5785,Dup,14:53:28.31,+52:04:34.0,Boo -NGC5786,G,14:58:56.26,-42:00:48.1,Cen -NGC5787,G,14:55:15.58,+42:30:24.7,Boo -NGC5788,G,14:53:16.99,+52:02:39.3,Boo -NGC5789,G,14:56:35.52,+30:14:02.5,Boo -NGC5790,G,14:57:35.81,+08:17:07.0,Boo -NGC5791,G,14:58:46.22,-19:16:00.7,Lib -NGC5792,G,14:58:22.71,-01:05:27.9,Lib -NGC5793,G,14:59:24.76,-16:41:36.1,Lib -NGC5794,G,14:55:53.63,+49:43:33.9,Boo -NGC5795,G,14:56:19.88,+49:24:00.2,Boo -NGC5796,G,14:59:24.11,-16:37:25.9,Lib -NGC5797,G,14:56:24.05,+49:41:46.2,Boo -NGC5798,G,14:57:37.97,+29:58:06.6,Boo -NGC5799,G,15:05:35.21,-72:25:58.2,Aps -NGC5800,OCl,15:01:47.87,-51:55:06.9,Lup -NGC5801,G,15:00:25.94,-13:54:15.5,Lib -NGC5802,G,15:00:30.00,-13:55:08.5,Lib -NGC5803,G,15:00:34.54,-13:53:40.3,Lib -NGC5804,G,14:57:06.80,+49:40:08.5,Boo -NGC5805,GPair,14:57:11.90,+49:37:40.0,Boo -NGC5805 NED01,G,14:57:11.62,+49:37:44.4,Boo -NGC5805 NED02,G,14:57:12.13,+49:37:36.8,Boo -NGC5806,G,15:00:00.40,+01:53:28.7,Vir -NGC5807,G,14:55:48.67,+63:54:12.5,Dra -NGC5808,G,14:54:02.81,+73:07:54.1,UMi -NGC5809,G,15:00:52.33,-14:09:54.7,Lib -NGC5810,G,15:02:42.73,-17:52:04.8,Lib -NGC5811,GPair,15:00:26.90,+01:37:26.0,Vir -NGC5812,G,15:00:55.70,-07:27:26.5,Lib -NGC5813,G,15:01:11.23,+01:42:07.1,Vir -NGC5814,G,15:01:21.15,+01:38:13.5,Vir -NGC5815,G,15:00:29.29,-16:50:03.5,Lib -NGC5816,G,15:00:04.86,-16:05:37.1,Lib -NGC5817,G,14:59:40.85,-16:10:49.3,Lib -NGC5818,G,14:58:58.39,+49:49:17.1,Boo -NGC5819,Dup,14:54:02.81,+73:07:54.1,UMi -NGC5820,G,14:58:39.82,+53:53:09.9,Boo -NGC5821,G,14:58:59.66,+53:55:23.8,Boo -NGC5822,OCl,15:04:21.25,-54:23:47.1,Lup -NGC5823,OCl,15:05:30.63,-55:36:13.5,Cir -NGC5824,GCl,15:03:58.64,-33:04:05.3,Lup -NGC5825,Dup,14:54:31.49,+18:38:32.3,Boo -NGC5826,G,15:06:33.84,+55:28:44.8,Dra -NGC5827,G,15:01:53.73,+25:57:52.3,Boo -NGC5828,G,15:00:46.11,+49:59:37.2,Boo -NGC5829,G,15:02:42.01,+23:20:01.0,Boo -NGC5830,G,15:01:50.91,+47:52:31.4,Boo -NGC5831,G,15:04:07.00,+01:13:11.7,Vir -NGC5832,G,14:57:45.71,+71:40:56.4,UMi -NGC5833,G,15:11:53.65,-72:51:33.9,Aps -NGC5834,Dup,15:03:58.64,-33:04:05.3,Lup -NGC5835,G,15:02:25.37,+48:52:39.7,Boo -NGC5836,G,14:59:30.99,+73:53:35.6,UMi -NGC5837,G,15:04:40.58,+12:38:00.4,Boo -NGC5838,G,15:05:26.26,+02:05:57.6,Vir -NGC5839,G,15:05:27.49,+01:38:05.3,Vir -NGC5840,Other,15:04:20.55,+29:30:21.7,Boo -NGC5841,G,15:06:35.04,+02:00:17.3,Vir -NGC5842,G,15:04:51.99,+21:04:10.0,Boo -NGC5843,G,15:07:27.89,-36:19:38.7,Lup -NGC5844,PN,15:10:40.95,-64:40:25.2,TrA -NGC5845,G,15:06:00.81,+01:38:01.7,Vir -NGC5846,G,15:06:29.28,+01:36:20.2,Vir -NGC5846A,G,15:06:29.20,+01:35:41.5,Vir -NGC5847,G,15:06:22.27,+06:22:47.6,Vir -NGC5848,Dup,15:06:35.04,+02:00:17.3,Vir -NGC5849,G,15:06:50.69,-14:34:18.6,Lib -NGC5850,G,15:07:07.69,+01:32:39.3,Vir -NGC5851,G,15:06:53.42,+12:51:31.9,Boo -NGC5852,G,15:06:56.41,+12:50:48.6,Boo -NGC5853,G,15:05:53.29,+39:31:19.9,Boo -NGC5854,G,15:07:47.70,+02:34:07.1,Vir -NGC5855,G,15:07:49.04,+03:59:03.0,Vir -NGC5856,**,15:07:20.23,+18:26:32.8,Boo -NGC5857,G,15:07:27.31,+19:35:51.5,Boo -NGC5858,G,15:08:49.14,-11:12:28.2,Lib -NGC5859,G,15:07:34.74,+19:34:56.3,Boo -NGC5860,GPair,15:06:33.80,+42:38:29.0,Boo -NGC5860 NED01,G,15:06:33.62,+42:38:25.3,Boo -NGC5860 NED02,G,15:06:33.80,+42:38:34.1,Boo -NGC5861,G,15:09:16.09,-11:19:18.0,Lib -NGC5862,G,15:06:03.31,+55:34:25.9,Dra -NGC5863,G,15:10:48.36,-18:25:51.7,Lib -NGC5864,G,15:09:33.56,+03:03:09.9,Vir -NGC5865,G,15:09:49.18,+00:31:47.4,Vir -NGC5866,G,15:06:29.50,+55:45:47.6,Dra -NGC5866B,G,15:12:07.23,+55:47:06.3,Dra -NGC5867,G,15:06:24.32,+55:43:53.7,Dra -NGC5868,Dup,15:09:49.18,+00:31:47.4,Vir -NGC5869,G,15:09:49.44,+00:28:12.1,Vir -NGC5870,Dup,15:06:33.84,+55:28:44.8,Dra -NGC5871,*,15:10:04.79,+00:29:52.8,Vir -NGC5872,G,15:10:55.66,-11:28:48.5,Lib -NGC5873,PN,15:12:50.80,-38:07:32.0,Lup -NGC5874,G,15:07:51.83,+54:45:10.0,Boo -NGC5875,G,15:09:13.16,+52:31:42.4,Boo -NGC5876,G,15:09:31.56,+54:30:23.4,Boo -NGC5877,**,15:12:53.07,-04:55:38.0,Lib -NGC5878,G,15:13:45.71,-14:16:11.4,Lib -NGC5879,G,15:09:46.73,+57:00:00.7,Dra -NGC5880,G,15:15:01.14,-14:34:44.5,Lib -NGC5881,G,15:06:20.82,+62:58:51.5,Dra -NGC5882,PN,15:16:50.00,-45:38:57.5,Lup -NGC5883,G,15:15:10.18,-14:37:01.4,Lib -NGC5884,**,15:13:09.13,+31:51:39.8,Boo -NGC5885,G,15:15:04.16,-10:05:09.6,Lib -NGC5886,G,15:12:45.44,+41:14:00.9,Boo -NGC5887,G,15:14:43.98,+01:09:15.3,Se1 -NGC5888,G,15:13:07.37,+41:15:52.7,Boo -NGC5889,G,15:13:15.75,+41:19:40.7,Boo -NGC5890,G,15:17:51.17,-17:35:21.1,Lib -NGC5891,G,15:16:13.40,-11:29:39.3,Lib -NGC5892,G,15:13:48.21,-15:27:50.0,Lib -NGC5893,G,15:13:34.17,+41:57:31.7,Boo -NGC5894,G,15:11:40.98,+59:48:32.2,Dra -NGC5895,G,15:13:50.00,+42:00:29.1,Boo -NGC5896,G,15:13:50.68,+42:01:27.4,Boo -NGC5897,GCl,15:17:24.40,-21:00:36.4,Lib -NGC5898,G,15:18:13.56,-24:05:52.6,Lib -NGC5899,G,15:15:03.22,+42:02:59.4,Boo -NGC5900,G,15:15:05.16,+42:12:33.9,Boo -NGC5901,*,15:15:02.60,+42:13:41.0,Boo -NGC5902,G,15:14:22.40,+50:19:46.8,Boo -NGC5903,G,15:18:36.53,-24:04:06.9,Lib -NGC5904,GCl,15:18:33.75,+02:04:57.7,Se1 -NGC5905,G,15:15:23.32,+55:31:02.5,Dra -NGC5906,Dup,15:15:53.77,+56:19:43.6,Dra -NGC5907,G,15:15:53.77,+56:19:43.6,Dra -NGC5908,G,15:16:43.22,+55:24:33.3,Dra -NGC5909,G,15:11:28.03,+75:23:01.7,UMi -NGC5910,GTrpl,15:19:25.10,+20:53:42.0,Se1 -NGC5910 NED01,G,15:19:24.26,+20:53:26.9,Se1 -NGC5910 NED02,G,15:19:24.74,+20:53:46.9,Se1 -NGC5910 NED03,G,15:19:25.83,+20:53:58.3,Se1 -NGC5911,G,15:20:18.22,+03:31:06.0,Se1 -NGC5912,G,15:11:40.90,+75:23:05.3,UMi -NGC5913,G,15:20:55.44,-02:34:40.6,Se1 -NGC5914,G,15:18:43.82,+41:51:55.7,Boo -NGC5914B,G,15:18:45.31,+41:53:26.9,Boo -NGC5915,G,15:21:33.08,-13:05:30.3,Lib -NGC5916,G,15:21:37.92,-13:10:09.4,Lib -NGC5916A,G,15:21:13.84,-13:06:02.2,Lib -NGC5917,G,15:21:32.57,-07:22:37.8,Lib -NGC5918,G,15:19:25.26,+45:52:48.9,Boo -NGC5919,G,15:21:36.88,+07:43:09.5,Se1 -NGC5920,G,15:21:51.85,+07:42:31.7,Se1 -NGC5921,G,15:21:56.56,+05:04:13.9,Se1 -NGC5922,Dup,15:21:14.24,+41:43:33.4,Boo -NGC5923,G,15:21:14.24,+41:43:33.4,Boo -NGC5924,G,15:22:01.98,+31:13:59.1,CrB -NGC5925,OCl,15:27:26.82,-54:31:43.6,Nor -NGC5926,G,15:23:24.90,+12:42:55.0,Se1 -NGC5927,GCl,15:28:00.43,-50:40:22.0,Lup -NGC5928,G,15:26:02.88,+18:04:25.1,Se1 -NGC5929,G,15:26:06.16,+41:40:14.4,Boo -NGC5930,G,15:26:07.94,+41:40:33.8,Boo -NGC5931,G,15:29:29.63,+07:34:23.7,Se1 -NGC5932,G,15:26:48.21,+48:36:53.9,Boo -NGC5933,G,15:27:01.57,+48:36:47.9,Boo -NGC5934,G,15:28:12.78,+42:55:47.7,Boo -NGC5935,G,15:28:16.71,+42:56:38.8,Boo -NGC5936,G,15:30:00.83,+12:59:21.6,Se1 -NGC5937,G,15:30:46.13,-02:49:46.2,Se1 -NGC5938,G,15:36:26.27,-66:51:35.1,TrA -NGC5939,G,15:24:46.03,+68:43:50.3,UMi -NGC5940,G,15:31:18.07,+07:27:27.9,Se1 -NGC5941,G,15:31:40.24,+07:20:20.4,Se1 -NGC5942,G,15:31:36.82,+07:18:44.7,Se1 -NGC5943,G,15:29:44.12,+42:46:40.7,Boo -NGC5944,G,15:31:47.60,+07:18:29.3,Se1 -NGC5945,G,15:29:45.02,+42:55:07.2,Boo -NGC5946,GCl,15:35:28.57,-50:39:35.0,Nor -NGC5947,G,15:30:36.60,+42:43:01.7,Boo -NGC5948,**,15:32:58.64,+03:58:58.2,Se1 -NGC5949,G,15:28:00.67,+64:45:47.5,Dra -NGC5950,G,15:31:30.79,+40:25:48.1,Boo -NGC5951,G,15:33:43.06,+15:00:26.2,Se1 -NGC5952,G,15:34:56.44,+04:57:31.8,Se1 -NGC5953,G,15:34:32.38,+15:11:37.6,Se1 -NGC5954,G,15:34:35.02,+15:12:00.2,Se1 -NGC5955,G,15:35:12.48,+05:03:46.8,Se1 -NGC5956,G,15:34:58.54,+11:45:01.0,Se1 -NGC5957,G,15:35:23.21,+12:02:51.4,Se1 -NGC5958,G,15:34:49.15,+28:39:18.9,CrB -NGC5959,G,15:37:22.43,-16:35:44.8,Lib -NGC5960,G,15:36:18.43,+05:39:55.3,Se1 -NGC5961,G,15:35:16.32,+30:51:51.0,CrB -NGC5962,G,15:36:31.68,+16:36:28.0,Se1 -NGC5963,G,15:33:27.86,+56:33:34.9,Dra -NGC5964,G,15:37:36.22,+05:58:26.5,Se1 -NGC5965,G,15:34:02.46,+56:41:08.2,Dra -NGC5966,G,15:35:52.14,+39:46:08.2,Boo -NGC5967,G,15:48:15.93,-75:40:22.6,Aps -NGC5967A,G,15:46:58.72,-75:47:14.8,Aps -NGC5968,G,15:39:57.17,-30:33:10.0,Lup -NGC5969,G,15:34:51.02,+56:27:03.9,Dra -NGC5970,G,15:38:29.97,+12:11:11.8,Se1 -NGC5971,G,15:35:36.89,+56:27:42.1,Dra -NGC5972,G,15:38:54.17,+17:01:34.3,Se1 -NGC5973,G,15:40:15.47,-08:36:03.4,Lib -NGC5974,G,15:39:02.42,+31:45:35.7,CrB -NGC5975,G,15:39:57.97,+21:28:14.9,Se1 -NGC5976,G,15:36:47.97,+59:23:51.9,Dra -NGC5977,G,15:40:33.45,+17:07:40.7,Se1 -NGC5978,G,15:42:27.22,-13:14:04.0,Lib -NGC5979,PN,15:47:41.06,-61:13:04.3,TrA -NGC5980,G,15:41:30.40,+15:47:15.6,Se1 -NGC5981,G,15:37:53.45,+59:23:30.3,Dra -NGC5982,G,15:38:39.83,+59:21:21.0,Dra -NGC5983,G,15:42:45.63,+08:14:28.0,Se1 -NGC5984,G,15:42:53.17,+14:13:53.3,Se1 -NGC5985,G,15:39:37.09,+59:19:55.0,Dra -NGC5986,GCl,15:46:03.44,-37:47:10.1,Lup -NGC5987,G,15:39:57.37,+58:04:46.3,Dra -NGC5988,G,15:44:33.86,+10:17:35.3,Se1 -NGC5989,G,15:41:32.80,+59:45:18.8,Dra -NGC5990,G,15:46:16.36,+02:24:55.5,Se1 -NGC5991,G,15:45:16.78,+24:37:49.9,Se1 -NGC5992,G,15:44:21.51,+41:05:10.9,Boo -NGC5993,G,15:44:27.62,+41:07:14.9,Boo -NGC5994,G,15:46:53.26,+17:52:21.4,Se1 -NGC5995,G,15:48:24.95,-13:45:28.0,Lib -NGC5996,G,15:46:58.88,+17:53:03.1,Se1 -NGC5997,G,15:47:27.67,+08:19:16.3,Se1 -NGC5998,OCl,15:49:38.21,-28:34:41.6,Sco -NGC5999,OCl,15:52:08.63,-56:28:22.1,Nor -NGC6000,G,15:49:49.55,-29:23:12.6,Sco -NGC6001,G,15:47:45.96,+28:38:30.1,CrB -NGC6002,*,15:47:44.39,+28:36:34.9,CrB -NGC6003,G,15:49:25.64,+19:01:55.5,Se1 -NGC6004,G,15:50:22.72,+18:56:21.4,Se1 -NGC6005,OCl,15:55:48.70,-57:26:14.6,Nor -NGC6006,G,15:53:02.57,+12:00:19.3,Se1 -NGC6007,G,15:53:23.18,+11:57:33.1,Se1 -NGC6008,G,15:52:56.03,+21:06:01.8,Se1 -NGC6009,G,15:53:24.08,+12:03:31.2,Se1 -NGC6010,G,15:54:19.15,+00:32:35.0,Se1 -NGC6011,G,15:46:32.89,+72:10:09.2,UMi -NGC6012,G,15:54:13.94,+14:36:04.5,Se1 -NGC6013,G,15:52:52.84,+40:38:48.2,Her -NGC6014,G,15:55:57.40,+05:55:54.8,Se1 -NGC6015,G,15:51:25.23,+62:18:36.1,Dra -NGC6016,G,15:55:54.87,+26:57:59.4,CrB -NGC6017,G,15:57:15.44,+05:59:54.2,Se1 -NGC6018,G,15:57:29.77,+15:52:21.4,Se1 -NGC6019,G,15:52:09.14,+64:50:26.4,Dra -NGC6020,G,15:57:08.14,+22:24:16.5,Se1 -NGC6021,G,15:57:30.69,+15:57:21.8,Se1 -NGC6022,G,15:57:47.77,+16:16:56.3,Se1 -NGC6023,G,15:57:49.62,+16:18:36.7,Se1 -NGC6024,G,15:53:07.87,+64:55:05.0,Dra -NGC6025,OCl,16:03:17.79,-60:25:52.9,TrA -NGC6026,PN,16:01:20.91,-34:32:39.2,Lup -NGC6027,G,15:59:12.54,+20:45:48.1,Se1 -NGC6027A,G,15:59:11.14,+20:45:17.5,Se1 -NGC6027B,G,15:59:10.83,+20:45:43.9,Se1 -NGC6027C,G,15:59:11.87,+20:44:51.4,Se1 -NGC6027D,G,15:59:12.90,+20:45:35.7,Se1 -NGC6027E,G,15:59:14.48,+20:45:57.3,Se1 -NGC6028,G,16:01:28.96,+19:21:35.6,Her -NGC6029,G,16:01:58.87,+12:34:26.4,Se1 -NGC6030,G,16:01:51.42,+17:57:27.0,Her -NGC6031,OCl,16:07:35.38,-54:00:53.8,Nor -NGC6032,G,16:03:01.12,+20:57:21.4,Her -NGC6033,G,16:04:27.98,-02:07:15.5,Se1 -NGC6034,G,16:03:32.08,+17:11:55.3,Her -NGC6035,G,16:03:24.18,+20:53:28.5,Her -NGC6036,G,16:04:30.75,+03:52:06.5,Se1 -NGC6037,G,16:04:29.90,+03:48:54.5,Se1 -NGC6038,G,16:02:40.55,+37:21:34.2,CrB -NGC6039,G,16:04:39.57,+17:42:03.1,Her -NGC6040,GPair,16:04:26.63,+17:44:42.5,Her -NGC6041,GPair,16:04:35.28,+17:43:09.3,Her -NGC6042,Dup,16:04:39.57,+17:42:03.1,Her -NGC6043,GPair,16:05:01.20,+17:46:26.0,Her -NGC6044,G,16:04:59.68,+17:52:13.3,Her -NGC6045,G,16:05:07.89,+17:45:27.6,Her -NGC6046,Dup,16:01:28.96,+19:21:35.6,Her -NGC6047,G,16:05:08.99,+17:43:47.6,Her -NGC6048,G,15:57:30.25,+70:41:20.8,UMi -NGC6049,*,16:05:37.93,+08:05:46.5,Se1 -NGC6050,G,16:05:23.37,+17:45:25.8,Her -NGC6050B,G,16:05:22.22,+17:45:15.1,Her -NGC6051,G,16:04:56.67,+23:55:57.7,Se1 -NGC6052,GPair,16:05:12.99,+20:32:32.5,Her -NGC6052 NED01,G,16:05:12.87,+20:32:32.6,Her -NGC6052 NED02,G,16:05:13.23,+20:32:32.7,Her -NGC6053,Dup,16:05:32.56,+18:09:34.5,Her -NGC6054,G,16:05:38.15,+17:46:04.4,Her -NGC6055,G,16:05:39.65,+18:09:51.7,Her -NGC6056,G,16:05:31.28,+17:57:49.1,Her -NGC6057,G,16:05:32.56,+18:09:34.5,Her -NGC6058,PN,16:04:26.54,+40:40:59.0,Her -NGC6059,**,16:06:48.00,-06:23:35.0,Oph -NGC6060,G,16:05:51.98,+21:29:05.9,Her -NGC6061,G,16:06:16.03,+18:14:59.8,Her -NGC6062,G,16:06:22.82,+19:46:40.7,Her -NGC6062B,G,16:06:18.97,+19:45:47.7,Her -NGC6063,G,16:07:12.99,+07:58:44.4,Se1 -NGC6064,Dup,16:05:12.99,+20:32:32.5,Her -NGC6065,G,16:07:22.96,+13:53:16.5,Se1 -NGC6066,G,16:07:35.35,+13:56:37.4,Se1 -NGC6067,OCl,16:13:11.05,-54:13:08.2,Nor -NGC6068,G,15:55:25.99,+78:59:48.4,UMi -NGC6068A,G,15:54:47.51,+78:59:06.4,UMi -NGC6069,G,16:07:41.64,+38:55:50.7,CrB -NGC6070,G,16:09:58.69,+00:42:33.5,Se1 -NGC6071,G,16:02:07.03,+70:25:01.3,UMi -NGC6072,PN,16:12:58.20,-36:13:48.0,Sco -NGC6073,G,16:10:10.85,+16:41:58.6,Her -NGC6074,GPair,16:11:17.00,+14:15:25.0,Her -NGC6074 NED01,G,16:11:16.76,+14:15:18.1,Her -NGC6074 NED02,G,16:11:17.22,+14:15:31.5,Her -NGC6075,G,16:11:22.59,+23:57:54.0,Her -NGC6076,GPair,16:11:13.40,+26:52:21.0,CrB -NGC6076 NED01,G,16:11:13.01,+26:52:19.3,CrB -NGC6076 NED02,G,16:11:13.80,+26:52:24.0,CrB -NGC6077,G,16:11:14.12,+26:55:24.3,CrB -NGC6078,G,16:12:05.45,+14:12:31.6,Her -NGC6079,G,16:04:29.21,+69:39:56.8,UMi -NGC6080,GPair,16:12:59.00,+02:10:45.0,Se1 -NGC6080 NED01,G,16:12:58.64,+02:10:38.2,Se1 -NGC6080 NED02,G,16:12:59.46,+02:10:51.8,Se1 -NGC6081,G,16:12:56.86,+09:52:01.6,Her -NGC6082,Other,16:15:27.56,-34:13:57.9,Sco -NGC6083,G,16:13:12.66,+14:11:07.5,Her -NGC6084,G,16:14:16.72,+17:45:26.8,Her -NGC6085,G,16:12:35.22,+29:21:54.3,CrB -NGC6086,G,16:12:35.53,+29:29:05.2,CrB -NGC6087,OCl,16:18:50.58,-57:56:04.5,Nor -NGC6088,GPair,16:10:43.50,+57:27:51.0,Dra -NGC6088 NED01,G,16:10:42.63,+57:27:59.5,Dra -NGC6088 NED02,G,16:10:44.38,+57:27:43.7,Dra -NGC6089,GPair,16:12:40.70,+33:02:11.0,CrB -NGC6089 NED01,G,16:12:40.15,+33:02:05.6,CrB -NGC6089 NED02,G,16:12:41.34,+33:02:15.8,CrB -NGC6090,GPair,16:11:40.70,+52:27:24.0,Dra -NGC6090 NED01,G,16:11:40.33,+52:27:23.7,Dra -NGC6090 NED02,G,16:11:40.82,+52:27:26.9,Dra -NGC6091,G,16:07:52.99,+69:54:17.2,UMi -NGC6092,**,16:14:04.60,+28:07:32.2,CrB -NGC6093,GCl,16:17:02.51,-22:58:30.4,Sco -NGC6094,G,16:06:33.90,+72:29:39.8,UMi -NGC6095,G,16:11:10.97,+61:16:04.7,Dra -NGC6096,G,16:14:46.70,+26:33:31.8,CrB -NGC6097,G,16:14:26.17,+35:06:33.0,CrB -NGC6098,G,16:15:34.18,+19:27:43.1,Her -NGC6099,G,16:15:35.56,+19:27:12.2,Her -NGC6100,G,16:16:52.37,+00:50:28.7,Se1 -NGC6101,GCl,16:25:48.57,-72:12:05.6,Aps -NGC6102,G,16:15:36.92,+28:09:30.9,CrB -NGC6103,G,16:15:44.62,+31:57:50.2,CrB -NGC6104,G,16:16:30.69,+35:42:29.0,CrB -NGC6105,G,16:17:09.31,+34:52:44.0,CrB -NGC6106,G,16:18:47.17,+07:24:39.2,Her -NGC6107,G,16:17:20.13,+34:54:07.0,CrB -NGC6108,G,16:17:25.63,+35:08:08.6,CrB -NGC6109,G,16:17:40.52,+35:00:15.4,CrB -NGC6110,G,16:17:43.97,+35:05:13.1,CrB -NGC6111,GPair,16:14:22.80,+63:15:40.0,Dra -NGC6112,G,16:18:00.56,+35:06:37.0,CrB -NGC6113,G,16:19:10.54,+14:08:01.1,Her -NGC6114,G,16:18:23.63,+35:10:27.5,CrB -NGC6115,OCl,16:24:26.38,-51:56:53.7,Nor -NGC6116,G,16:18:54.60,+35:09:14.3,CrB -NGC6117,G,16:19:18.16,+37:05:42.8,CrB -NGC6118,G,16:21:48.62,-02:17:00.4,Se1 -NGC6119,G,16:19:41.97,+37:48:22.7,CrB -NGC6120,G,16:19:48.10,+37:46:28.1,CrB -NGC6121,GCl,16:23:35.40,-26:31:31.9,Sco -NGC6122,G,16:20:09.53,+37:47:53.6,CrB -NGC6123,G,16:17:19.74,+61:56:20.8,Dra -NGC6124,OCl,16:25:20.06,-40:39:13.3,Sco -NGC6125,G,16:19:11.55,+57:59:03.2,Dra -NGC6126,G,16:21:27.93,+36:22:35.9,CrB -NGC6127,Dup,16:19:11.55,+57:59:03.2,Dra -NGC6128,Dup,16:19:11.55,+57:59:03.2,Dra -NGC6129,G,16:21:43.27,+37:59:45.7,CrB -NGC6130,G,16:19:33.44,+57:36:53.9,Dra -NGC6131,G,16:21:52.25,+38:55:56.8,CrB -NGC6132,G,16:23:38.84,+11:47:10.5,Her -NGC6133,Other,16:20:17.21,+56:39:08.7,Dra -NGC6134,OCl,16:27:46.50,-49:09:04.2,Nor -NGC6135,G,16:14:24.88,+64:58:57.9,Dra -NGC6136,G,16:20:59.42,+55:58:13.8,Dra -NGC6137,G,16:23:03.10,+37:55:20.5,CrB -NGC6138,Dup,17:22:40.02,+41:06:06.1,Her -NGC6139,GCl,16:27:40.51,-38:50:59.1,Sco -NGC6140,G,16:20:58.16,+65:23:26.0,Dra -NGC6141,G,16:23:06.41,+40:51:29.9,Her -NGC6142,G,16:23:21.07,+37:15:30.2,CrB -NGC6143,G,16:21:42.29,+55:05:09.8,Dra -NGC6144,GCl,16:27:14.14,-26:01:29.0,Sco -NGC6145,G,16:25:02.36,+40:56:47.9,Her -NGC6146,G,16:25:10.36,+40:53:34.3,Her -NGC6147,G,16:25:05.84,+40:55:43.6,Her -NGC6148,G,16:27:04.03,+24:05:35.8,Her -NGC6149,G,16:27:24.23,+19:35:49.9,Her -NGC6150,G,16:25:50.03,+40:29:18.8,Her -NGC6151,Other,16:38:24.22,-73:15:08.8,Aps -NGC6152,OCl,16:32:45.62,-52:38:38.4,Nor -NGC6153,PN,16:31:30.64,-40:15:12.4,Sco -NGC6154,G,16:25:30.48,+49:50:24.9,Her -NGC6155,G,16:26:08.34,+48:22:00.5,Her -NGC6156,G,16:34:52.55,-60:37:07.7,TrA -NGC6157,G,16:25:48.39,+55:21:38.1,Dra -NGC6158,G,16:27:40.89,+39:22:58.8,Her -NGC6159,G,16:27:25.23,+42:40:47.0,Her -NGC6160,G,16:27:41.13,+40:55:37.2,Her -NGC6161,G,16:28:20.63,+32:48:38.3,Her -NGC6162,G,16:28:22.37,+32:50:57.6,Her -NGC6163,G,16:28:27.91,+32:50:47.0,Her -NGC6164,Neb,16:33:41.84,-48:04:48.2,Nor -NGC6165,Neb,16:34:03.45,-48:09:01.8,Nor -NGC6166,G,16:28:38.48,+39:33:05.6,Her -NGC6166B,G,16:28:53.21,+39:33:36.0,Her -NGC6166C,G,16:28:23.31,+39:34:13.1,Her -NGC6167,OCl,16:34:34.98,-49:46:18.8,Nor -NGC6168,G,16:31:21.30,+20:11:05.8,Her -NGC6169,OCl,16:34:04.63,-44:02:44.3,Nor -NGC6170,G,16:27:36.50,+59:33:45.0,Dra -NGC6171,GCl,16:32:31.92,-13:03:13.1,Oph -NGC6172,G,16:22:10.30,-01:30:53.5,Se1 -NGC6173,G,16:29:44.90,+40:48:41.8,Her -NGC6174,GPair,16:29:47.68,+40:52:19.1,Her -NGC6174 NED01,G,16:29:46.50,+40:52:29.5,Her -NGC6174 NED02,G,16:29:47.32,+40:52:21.8,Her -NGC6175,GPair,16:29:57.90,+40:37:45.0,Her -NGC6175 NED01,G,16:29:57.53,+40:37:50.6,Her -NGC6175 NED02,G,16:29:58.06,+40:37:42.8,Her -NGC6176,Dup,16:27:36.50,+59:33:45.0,Dra -NGC6177,G,16:30:38.88,+35:03:23.2,Her -NGC6178,OCl,16:35:47.25,-45:38:37.5,Sco -NGC6179,G,16:30:47.05,+35:06:08.1,Her -NGC6180,G,16:30:33.92,+40:32:22.0,Her -NGC6181,G,16:32:20.96,+19:49:35.6,Her -NGC6182,G,16:29:34.00,+55:31:04.0,Dra -NGC6183,G,16:41:41.90,-69:22:19.8,TrA -NGC6184,G,16:31:34.53,+40:33:56.2,Her -NGC6185,G,16:33:17.83,+35:20:32.4,Her -NGC6186,G,16:34:25.48,+21:32:27.2,Her -NGC6187,G,16:31:36.70,+57:42:24.0,Dra -NGC6188,Neb,16:40:05.84,-48:39:44.2,Ara -NGC6189,G,16:31:40.90,+59:37:34.0,Dra -NGC6190,G,16:32:06.70,+58:26:20.3,Dra -NGC6191,G,16:11:30.49,+58:47:09.0,Dra -NGC6192,OCl,16:40:23.87,-43:22:00.5,Sco -NGC6193,OCl,16:41:20.23,-48:45:45.1,Ara -NGC6194,G,16:36:37.15,+36:12:01.6,Her -NGC6195,G,16:36:32.57,+39:01:40.4,Her -NGC6196,G,16:37:53.92,+36:04:23.0,Her -NGC6197,G,16:37:59.88,+35:59:43.8,Her -NGC6198,G,16:35:30.65,+57:29:12.4,Dra -NGC6199,*,16:39:28.97,+36:03:32.3,Her -NGC6200,OCl,16:44:07.35,-47:27:45.6,Ara -NGC6201,G,16:40:14.41,+23:45:55.2,Her -NGC6202,G,16:43:23.23,+61:59:02.2,Dra -NGC6203,G,16:40:27.41,+23:46:29.4,Her -NGC6204,OCl,16:46:09.50,-47:01:01.1,Ara -NGC6205,GCl,16:41:41.63,+36:27:40.7,Her -NGC6206,G,16:40:07.90,+58:37:02.5,Dra -NGC6207,G,16:43:03.75,+36:49:56.7,Her -NGC6208,OCl,16:49:28.19,-53:43:42.0,Ara -NGC6209,G,16:54:57.66,-72:35:11.9,Aps -NGC6210,PN,16:44:29.52,+23:47:59.4,Her -NGC6211,G,16:41:27.64,+57:47:01.1,Dra -NGC6212,G,16:43:23.14,+39:48:23.2,Her -NGC6213,G,16:41:37.19,+57:48:53.5,Dra -NGC6214,G,16:39:31.93,+66:02:22.3,Dra -NGC6215,G,16:51:06.81,-58:59:36.5,Ara -NGC6215A,G,16:52:59.45,-58:56:52.8,Ara -NGC6216,OCl,16:49:23.61,-44:43:53.5,Sco -NGC6217,G,16:32:39.20,+78:11:53.4,UMi -NGC6218,GCl,16:47:14.52,-01:56:52.2,Oph -NGC6219,G,16:46:22.51,+09:02:16.3,Her -NGC6220,G,16:47:13.28,-00:16:31.7,Oph -NGC6221,G,16:52:46.08,-59:13:07.0,Ara -NGC6222,Dup,17:00:45.40,-44:39:17.9,Sco -NGC6223,G,16:43:04.31,+61:34:44.1,Dra -NGC6224,G,16:48:18.54,+06:18:44.0,Her -NGC6225,G,16:48:21.58,+06:13:22.0,Her -NGC6226,Dup,16:43:23.23,+61:59:02.2,Dra -NGC6227,*,16:51:33.72,-41:13:49.9,Sco -NGC6228,G,16:48:02.66,+26:12:48.8,Her -NGC6229,GCl,16:46:58.86,+47:31:40.1,Her -NGC6230,GPair,16:50:44.70,+04:36:18.0,Her -NGC6230 NED01,G,16:50:42.73,+04:36:18.0,Her -NGC6230 NED02,G,16:50:46.70,+04:36:16.7,Her -NGC6231,OCl,16:54:10.92,-41:49:27.3,Sco -NGC6232,G,16:43:20.24,+70:37:57.1,Dra -NGC6233,G,16:50:15.70,+23:34:47.4,Her -NGC6234,G,16:51:57.34,+04:23:00.7,Oph -NGC6235,GCl,16:53:25.36,-22:10:38.8,Oph -NGC6236,G,16:44:34.65,+70:46:48.8,Dra -NGC6237,Other,16:44:07.54,+70:38:05.3,Dra -NGC6238,G,16:47:16.63,+62:08:49.3,Dra -NGC6239,G,16:50:04.98,+42:44:22.9,Her -NGC6240,G,16:52:58.87,+02:24:03.3,Oph -NGC6241,G,16:50:10.95,+45:25:14.2,Her -NGC6242,OCl,16:55:33.44,-39:27:39.4,Sco -NGC6243,G,16:52:26.29,+23:19:57.3,Her -NGC6244,G,16:48:03.91,+62:12:01.6,Dra -NGC6245,Other,16:45:22.48,+70:48:16.5,Dra -NGC6246,G,16:49:52.72,+55:32:31.4,Dra -NGC6246A,G,16:50:13.98,+55:23:04.6,Dra -NGC6247,G,16:48:20.24,+62:58:35.1,Dra -NGC6248,G,16:46:21.99,+70:21:31.7,Dra -NGC6249,OCl,16:57:41.50,-44:48:42.8,Sco -NGC6250,Cl+N,16:57:56.07,-45:56:11.9,Ara -NGC6251,G,16:32:31.97,+82:32:16.4,UMi -NGC6252,G,16:32:40.57,+82:34:36.4,UMi -NGC6253,OCl,16:59:05.13,-52:42:31.7,Ara -NGC6254,GCl,16:57:08.99,-04:05:57.6,Oph -NGC6255,G,16:54:47.95,+36:30:04.0,Her -NGC6256,GCl,16:59:32.69,-37:07:17.1,Sco -NGC6257,G,16:56:03.51,+39:38:43.8,Her -NGC6258,G,16:52:29.79,+60:30:52.3,Dra -NGC6259,OCl,17:00:45.40,-44:39:17.9,Sco -NGC6260,G,16:51:50.61,+63:42:52.5,Dra -NGC6261,G,16:56:30.52,+27:58:39.0,Her -NGC6262,G,16:58:42.80,+57:05:54.2,Dra -NGC6263,G,16:56:43.20,+27:49:19.5,Her -NGC6264,G,16:57:16.13,+27:50:58.6,Her -NGC6265,G,16:57:29.10,+27:50:39.2,Her -NGC6266,GCl,17:01:12.60,-30:06:44.5,Oph -NGC6267,G,16:58:08.66,+22:59:06.4,Her -NGC6268,OCl,17:02:10.39,-39:43:41.6,Sco -NGC6269,G,16:57:58.09,+27:51:15.6,Her -NGC6270,G,16:58:44.05,+27:51:32.8,Her -NGC6271,G,16:58:50.77,+27:57:52.7,Her -NGC6272,G,16:58:58.26,+27:55:51.3,Her -NGC6273,GCl,17:02:37.68,-26:16:04.6,Oph -NGC6274,GPair,16:59:20.92,+29:56:37.8,Her -NGC6274 NED01,G,16:59:20.50,+29:56:47.1,Her -NGC6274 NED02,G,16:59:21.43,+29:56:26.0,Her -NGC6275,G,16:55:33.37,+63:14:31.9,Dra -NGC6276,G,17:00:45.09,+23:02:38.4,Her -NGC6277,*,17:00:48.89,+23:02:21.7,Her -NGC6278,G,17:00:50.33,+23:00:39.7,Her -NGC6279,G,16:59:01.43,+47:14:13.8,Her -NGC6280,GPair,17:01:57.49,+06:39:56.7,Oph -NGC6281,OCl,17:04:41.30,-37:59:06.8,Sco -NGC6282,G,17:00:47.11,+29:49:14.2,Her -NGC6283,G,16:59:26.57,+49:55:19.1,Her -NGC6284,GCl,17:04:28.75,-24:45:51.6,Oph -NGC6285,G,16:58:24.01,+58:57:21.5,Dra -NGC6286,G,16:58:31.38,+58:56:10.5,Dra -NGC6287,GCl,17:05:09.34,-22:42:28.8,Oph -NGC6288,G,16:57:24.45,+68:27:25.3,Dra -NGC6289,G,16:57:44.99,+68:30:53.0,Dra -NGC6290,G,17:00:56.43,+58:58:13.8,Dra -NGC6291,G,17:00:55.92,+58:56:15.2,Dra -NGC6292,G,17:03:03.47,+61:02:38.0,Dra -NGC6293,GCl,17:10:10.41,-26:34:54.3,Oph -NGC6294,**,17:10:16.23,-26:34:28.4,Oph -NGC6295,G,17:03:15.35,+60:20:15.9,Dra -NGC6296,G,17:08:44.47,+03:53:38.7,Oph -NGC6297,G,17:03:36.55,+62:01:32.2,Dra -NGC6298,Dup,17:03:36.55,+62:01:32.2,Dra -NGC6299,G,17:05:04.41,+62:27:28.2,Dra -NGC6300,G,17:16:59.47,-62:49:14.0,Ara -NGC6301,G,17:08:32.74,+42:20:20.8,Her -NGC6302,PN,17:13:44.63,-37:06:11.3,Sco -NGC6303,G,17:05:02.77,+68:49:39.3,Dra -NGC6304,GCl,17:14:32.51,-29:27:44.2,Oph -NGC6305,G,17:18:00.92,-59:10:19.5,Ara -NGC6306,G,17:07:36.98,+60:43:44.0,Dra -NGC6307,G,17:07:40.47,+60:45:02.8,Dra -NGC6308,G,17:11:59.71,+23:22:47.8,Her -NGC6309,PN,17:14:04.30,-12:54:38.0,Oph -NGC6310,G,17:07:57.47,+60:59:24.6,Dra -NGC6311,G,17:10:43.57,+41:39:04.0,Her -NGC6312,G,17:10:48.16,+42:17:15.7,Her -NGC6313,G,17:10:20.80,+48:19:54.3,Her -NGC6314,G,17:12:38.71,+23:16:12.2,Her -NGC6315,G,17:12:46.15,+23:13:24.8,Her -NGC6316,GCl,17:16:37.41,-28:08:24.1,Oph -NGC6317,G,17:08:59.51,+62:53:52.8,Dra -NGC6318,OCl,17:16:11.59,-39:25:29.9,Sco -NGC6319,G,17:09:44.08,+62:58:23.0,Dra -NGC6320,G,17:12:55.74,+40:15:59.4,Her -NGC6321,G,17:14:24.22,+20:18:50.1,Her -NGC6322,OCl,17:18:25.79,-42:56:02.5,Sco -NGC6323,G,17:13:18.08,+43:46:56.8,Her -NGC6324,G,17:05:25.32,+75:24:25.3,UMi -NGC6325,GCl,17:17:59.27,-23:45:57.7,Oph -NGC6326,PN,17:20:46.36,-51:45:15.8,Ara -NGC6327,G,17:14:02.29,+43:38:58.1,Her -NGC6328,G,17:23:41.03,-65:00:36.6,Ara -NGC6329,G,17:14:15.03,+43:41:04.9,Her -NGC6330,G,17:15:44.42,+29:24:15.5,Her -NGC6331,G,17:03:35.97,+78:37:44.4,UMi -NGC6332,G,17:15:02.90,+43:39:36.7,Her -NGC6333,GCl,17:19:11.78,-18:30:58.5,Oph -NGC6334,SNR,17:20:49.70,-36:06:09.8,Sco -NGC6335,*Ass,17:19:31.92,-30:09:51.0,Sco -NGC6336,G,17:16:16.57,+43:49:13.7,Her -NGC6337,PN,17:22:15.61,-38:29:01.4,Sco -NGC6338,G,17:15:22.99,+57:24:40.3,Dra -NGC6339,G,17:17:06.50,+40:50:41.9,Her -NGC6340,G,17:10:24.85,+72:18:16.0,Dra -NGC6341,GCl,17:17:07.27,+43:08:11.5,Her -NGC6342,GCl,17:21:10.14,-19:35:14.7,Oph -NGC6343,G,17:17:16.27,+41:03:09.8,Her -NGC6344,**,17:17:18.15,+42:26:03.1,Her -NGC6345,G,17:15:24.28,+57:21:01.1,Dra -NGC6346,G,17:15:24.49,+57:19:21.1,Dra -NGC6347,G,17:19:54.67,+16:39:38.5,Her -NGC6348,G,17:18:21.19,+41:38:51.4,Her -NGC6349,G,17:19:06.55,+36:03:39.3,Her -NGC6350,G,17:18:42.25,+41:41:39.6,Her -NGC6351,GPair,17:19:11.40,+36:03:38.0,Her -NGC6351 NED01,G,17:19:11.08,+36:03:37.5,Her -NGC6351 NED02,G,17:19:11.67,+36:03:39.8,Her -NGC6352,GCl,17:25:29.16,-48:25:21.7,Ara -NGC6353,Other,17:21:12.47,+15:41:18.8,Her -NGC6354,Other,17:24:34.28,-38:32:29.8,Sco -NGC6355,GCl,17:23:58.65,-26:21:12.3,Oph -NGC6356,GCl,17:23:34.99,-17:48:46.9,Oph -NGC6357,Cl+N,17:24:43.57,-34:12:04.8,Sco -NGC6358,G,17:18:53.01,+52:36:55.0,Dra -NGC6359,G,17:17:52.99,+61:46:50.9,Dra -NGC6360,*Ass,17:24:27.62,-29:52:17.7,Oph -NGC6361,G,17:18:41.09,+60:36:29.4,Dra -NGC6362,GCl,17:31:54.84,-67:02:52.3,Ara -NGC6363,G,17:22:40.02,+41:06:06.1,Her -NGC6364,G,17:24:27.33,+29:23:24.6,Her -NGC6365,GPair,17:22:43.70,+62:10:12.0,Dra -NGC6365A,G,17:22:43.81,+62:09:57.9,Dra -NGC6365B,G,17:22:43.53,+62:10:25.4,Dra -NGC6366,GCl,17:27:44.33,-05:04:35.9,Oph -NGC6367,G,17:25:09.01,+37:45:35.6,Her -NGC6368,G,17:27:11.54,+11:32:37.0,Oph -NGC6369,PN,17:29:20.50,-23:45:34.0,Oph -NGC6370,G,17:23:25.18,+56:58:28.3,Dra -NGC6371,G,17:27:20.59,+26:30:18.2,Her -NGC6372,G,17:27:31.85,+26:28:30.5,Her -NGC6373,G,17:24:08.09,+58:59:42.3,Dra -NGC6374,OCl,17:34:42.54,-32:34:52.9,Sco -NGC6375,G,17:29:21.89,+16:12:24.0,Her -NGC6376,G,17:25:19.18,+58:49:02.8,Dra -NGC6377,G,17:25:23.21,+58:49:21.9,Dra -NGC6378,G,17:30:41.98,+06:16:56.2,Oph -NGC6379,G,17:30:34.91,+16:17:19.5,Her -NGC6380,GCl,17:34:28.42,-39:04:10.8,Sco -NGC6381,G,17:27:16.85,+60:00:50.6,Dra -NGC6382,G,17:27:55.18,+56:52:07.4,Dra -NGC6383,Dup,17:34:42.54,-32:34:52.9,Sco -NGC6384,G,17:32:24.30,+07:03:37.0,Oph -NGC6385,G,17:28:01.41,+57:31:18.5,Dra -NGC6386,G,17:28:51.78,+52:43:24.1,Dra -NGC6387,G,17:28:23.83,+57:32:43.4,Dra -NGC6388,GCl,17:36:17.43,-44:44:08.2,Sco -NGC6389,G,17:32:39.77,+16:24:06.4,Her -NGC6390,G,17:28:28.09,+60:05:39.0,Dra -NGC6391,G,17:28:48.99,+58:51:03.2,Dra -NGC6392,G,17:43:30.37,-69:47:06.7,Aps -NGC6393,G,17:30:08.45,+59:31:54.5,Dra -NGC6394,G,17:30:21.42,+59:38:23.6,Dra -NGC6395,G,17:26:31.27,+71:05:46.6,Dra -NGC6396,OCl,17:37:36.34,-35:01:33.1,Sco -NGC6397,GCl,17:40:41.36,-53:40:25.3,Ara -NGC6398,G,17:42:43.74,-61:41:39.4,Pav -NGC6399,G,17:31:50.30,+59:36:55.9,Dra -NGC6400,OCl,17:40:12.80,-36:56:51.8,Sco -NGC6401,GCl,17:38:36.93,-23:54:31.6,Oph -NGC6402,GCl,17:37:36.16,-03:14:45.3,Oph -NGC6403,G,17:43:23.56,-61:40:55.6,Pav -NGC6404,OCl,17:39:37.36,-33:14:48.2,Sco -NGC6405,OCl,17:40:20.75,-32:15:15.0,Sco -NGC6406,**,17:38:19.05,+18:49:59.0,Her -NGC6407,G,17:44:57.69,-60:44:23.3,Pav -NGC6408,G,17:38:47.36,+18:52:40.3,Her -NGC6409,G,17:36:35.40,+50:45:57.2,Dra -NGC6410,**,17:35:20.48,+60:47:35.0,Dra -NGC6411,G,17:35:32.85,+60:48:48.2,Dra -NGC6412,G,17:29:37.51,+75:42:15.9,Dra -NGC6413,Other,17:40:40.72,+12:37:26.2,Oph -NGC6414,G,17:30:36.86,+74:22:34.1,Dra -NGC6415,*Ass,17:44:20.55,-35:04:15.7,Sco -NGC6416,OCl,17:44:19.99,-32:21:39.6,Sco -NGC6417,G,17:41:47.82,+23:40:19.9,Her -NGC6418,G,17:38:09.32,+58:42:53.7,Dra -NGC6419,G,17:36:05.90,+68:09:20.8,Dra -NGC6420,G,17:36:16.30,+68:03:08.5,Dra -NGC6421,*Ass,17:45:44.21,-33:41:33.5,Sco -NGC6422,G,17:36:30.01,+68:03:31.0,Dra -NGC6423,G,17:36:53.33,+68:10:17.3,Dra -NGC6424,G,17:36:12.07,+69:59:20.0,Dra -NGC6425,OCl,17:47:01.68,-31:31:45.8,Sco -NGC6426,GCl,17:44:54.71,+03:10:12.5,Oph -NGC6427,G,17:43:38.59,+25:29:38.1,Her -NGC6428,**,17:43:52.81,+25:33:17.2,Her -NGC6429,G,17:44:05.35,+25:21:02.6,Her -NGC6430,G,17:45:14.27,+18:08:20.2,Her -NGC6431,Dup,17:43:38.59,+25:29:38.1,Her -NGC6432,Other,17:47:22.41,-24:53:14.9,Sgr -NGC6433,G,17:43:56.21,+36:48:00.4,Her -NGC6434,G,17:36:48.82,+72:05:20.3,Dra -NGC6435,G,17:40:11.08,+62:38:30.3,Dra -NGC6436,G,17:41:13.24,+60:26:58.9,Dra -NGC6437,*Ass,17:48:21.12,-35:21:58.3,Sco -NGC6438,G,18:22:17.48,-85:24:07.4,Oct -NGC6438A,G,18:22:35.49,-85:24:22.8,Oct -NGC6439,PN,17:48:19.80,-16:28:44.0,Sgr -NGC6440,GCl,17:48:52.67,-20:21:34.5,Sgr -NGC6441,GCl,17:50:12.84,-37:03:03.9,Sco -NGC6442,G,17:46:51.34,+20:45:39.9,Her -NGC6443,G,17:44:33.85,+48:06:50.6,Her -NGC6444,OCl,17:49:35.18,-34:49:10.8,Sco -NGC6445,PN,17:49:15.06,-20:00:34.2,Sgr -NGC6446,G,17:46:07.51,+35:34:09.8,Her -NGC6447,G,17:46:17.23,+35:34:19.1,Her -NGC6448,Other,17:43:42.80,+53:32:46.0,Dra -NGC6449,G,17:43:46.34,+56:48:14.9,Dra -NGC6450,Other,17:47:32.36,+18:34:30.8,Her -NGC6451,OCl,17:50:40.64,-30:12:41.8,Sco -NGC6452,G,17:47:58.53,+20:50:16.1,Her -NGC6453,GCl,17:50:51.71,-34:35:59.6,Sco -NGC6454,G,17:44:56.61,+55:42:17.2,Dra -NGC6455,*Ass,17:51:08.10,-35:20:16.1,Sco -NGC6456,G,17:42:31.85,+67:35:32.3,Dra -NGC6457,G,17:42:52.84,+66:28:33.6,Dra -NGC6458,G,17:49:11.01,+20:48:15.4,Her -NGC6459,G,17:45:47.19,+55:46:36.3,Dra -NGC6460,G,17:49:30.36,+20:45:49.2,Her -NGC6461,G,17:39:56.51,+74:02:03.2,Dra -NGC6462,G,17:44:48.86,+61:54:38.1,Dra -NGC6463,G,17:43:34.32,+67:36:12.6,Dra -NGC6464,G,17:45:47.53,+60:53:50.9,Dra -NGC6465,Other,17:52:55.58,-25:23:51.7,Sgr -NGC6466,G,17:48:08.10,+51:23:57.2,Dra -NGC6467,G,17:50:40.16,+17:32:16.0,Her -NGC6468,Dup,17:50:40.16,+17:32:16.0,Her -NGC6469,OCl,17:53:12.13,-22:16:30.4,Sgr -NGC6470,G,17:44:14.87,+67:37:09.8,Dra -NGC6471,GPair,17:44:15.60,+67:35:31.0,Dra -NGC6471 NED01,G,17:44:13.04,+67:35:35.0,Dra -NGC6471 NED02,G,17:44:18.16,+67:35:27.9,Dra -NGC6472,G,17:44:03.10,+67:37:49.2,Dra -NGC6473,G,17:45:57.84,+57:15:25.6,Dra -NGC6474,G,17:47:05.56,+57:18:04.2,Dra -NGC6475,OCl,17:53:51.18,-34:47:34.2,Sco -NGC6476,*Ass,17:54:02.01,-29:08:39.0,Sgr -NGC6477,G,17:44:30.02,+67:36:38.1,Dra -NGC6478,G,17:48:38.35,+51:09:26.1,Dra -NGC6479,G,17:48:21.59,+54:08:56.4,Dra -NGC6480,*Ass,17:54:26.04,-30:27:07.4,Sco -NGC6481,Other,17:52:48.91,+04:10:04.2,Oph -NGC6482,G,17:51:48.81,+23:04:19.0,Her -NGC6483,G,17:59:30.81,-63:40:07.4,Pav -NGC6484,G,17:51:46.98,+24:29:00.5,Her -NGC6485,G,17:51:52.74,+31:27:42.4,Her -NGC6486,G,17:52:35.30,+29:49:04.8,Her -NGC6487,G,17:52:41.85,+29:50:19.1,Her -NGC6488,G,17:49:20.97,+62:13:22.5,Dra -NGC6489,G,17:50:01.31,+60:05:32.0,Dra -NGC6490,G,17:54:30.47,+18:22:33.0,Her -NGC6491,G,17:50:00.70,+61:31:54.4,Dra -NGC6492,G,18:02:48.35,-66:25:50.3,Pav -NGC6493,G,17:50:22.66,+61:33:33.9,Dra -NGC6494,OCl,17:57:04.77,-18:59:07.2,Sgr -NGC6495,G,17:54:50.76,+18:19:36.9,Her -NGC6496,GCl,17:59:03.70,-44:15:58.7,Sco -NGC6497,G,17:51:17.97,+59:28:15.2,Dra -NGC6498,Dup,17:51:17.97,+59:28:15.2,Dra -NGC6499,**,17:55:20.01,+18:21:35.1,Her -NGC6500,G,17:55:59.78,+18:20:17.7,Her -NGC6501,G,17:56:03.74,+18:22:23.1,Her -NGC6502,G,18:04:13.72,-65:24:35.8,Pav -NGC6503,G,17:49:26.43,+70:08:39.7,Dra -NGC6504,G,17:56:05.71,+33:12:30.2,Her -NGC6505,G,17:51:07.44,+65:31:50.8,Dra -NGC6506,OCl,17:59:53.49,-24:41:07.2,Sgr -NGC6507,OCl,17:59:50.79,-17:27:01.0,Sgr -NGC6508,G,17:49:46.44,+72:01:16.0,Dra -NGC6509,G,17:59:25.27,+06:17:13.3,Oph -NGC6510,G,17:54:39.30,+60:49:04.5,Dra -NGC6511,Dup,17:54:39.30,+60:49:04.5,Dra -NGC6512,G,17:54:50.31,+62:38:42.3,Dra -NGC6513,G,17:59:34.53,+24:52:49.9,Her -NGC6514,Neb,18:02:42.11,-22:58:18.8,Sgr -NGC6515,G,17:57:25.19,+50:43:41.2,Dra -NGC6516,G,17:55:16.78,+62:40:11.5,Dra -NGC6517,GCl,18:01:50.39,-08:57:34.2,Oph -NGC6518,G,17:59:43.74,+28:52:00.0,Her -NGC6519,**,18:03:20.13,-29:48:15.3,Sgr -NGC6520,OCl,18:03:24.14,-27:53:10.0,Sgr -NGC6521,G,17:55:48.44,+62:36:44.1,Dra -NGC6522,GCl,18:03:34.07,-30:02:02.3,Sgr -NGC6523,Neb,18:03:41.27,-24:22:48.6,Sgr -NGC6524,G,17:59:14.71,+45:53:13.5,Her -NGC6525,OCl,18:02:04.75,+11:02:18.0,Oph -NGC6526,Neb,18:04:06.15,-24:26:30.8,Sgr -NGC6527,G,18:01:46.33,+19:43:43.4,Her -NGC6528,GCl,18:04:49.61,-30:03:20.8,Sgr -NGC6529,*Ass,18:05:28.86,-36:17:43.4,Sgr -NGC6530,Cl+N,18:04:31.03,-24:21:29.0,Sgr -NGC6531,OCl,18:04:13.45,-22:29:24.2,Sgr -NGC6532,G,17:59:13.95,+56:13:54.4,Dra -NGC6533,Dup,18:03:41.27,-24:22:48.6,Sgr -NGC6534,G,17:56:08.56,+64:17:01.3,Dra -NGC6535,GCl,18:03:50.69,-00:17:48.9,Se2 -NGC6536,G,17:57:16.35,+64:56:17.1,Dra -NGC6537,PN,18:05:13.10,-19:50:34.7,Sgr -NGC6538,G,17:54:16.61,+73:25:26.2,Dra -NGC6539,GCl,18:04:49.75,-07:35:09.1,Se2 -NGC6540,GCl,18:06:07.95,-27:45:46.1,Sgr -NGC6541,GCl,18:08:02.33,-43:42:57.2,CrA -NGC6542,G,17:59:38.62,+61:21:33.8,Dra -NGC6543,PN,17:58:33.39,+66:37:59.5,Dra -NGC6544,GCl,18:07:20.00,-24:59:54.1,Sgr -NGC6545,G,18:12:14.77,-63:46:34.1,Pav -NGC6546,OCl,18:07:22.55,-23:17:46.4,Sgr -NGC6547,G,18:05:10.02,+25:13:57.4,Her -NGC6548,G,18:05:59.24,+18:35:14.1,Her -NGC6549,G,18:05:49.50,+18:32:16.9,Her -NGC6550,Dup,18:05:49.50,+18:32:16.9,Her -NGC6551,Other,18:08:59.66,-29:33:27.6,Sgr -NGC6552,G,18:00:07.23,+66:36:54.4,Dra -NGC6553,GCl,18:09:17.48,-25:54:28.3,Sgr -NGC6554,OCl,18:09:23.98,-18:22:43.3,Sgr -NGC6555,G,18:07:49.17,+17:36:17.6,Her -NGC6556,*Ass,18:09:57.59,-27:31:29.3,Sgr -NGC6557,G,18:21:24.82,-76:34:58.7,Oct -NGC6558,GCl,18:10:18.38,-31:45:48.5,Sgr -NGC6559,Neb,18:09:56.85,-24:06:23.0,Sgr -NGC6560,G,18:05:13.98,+46:52:53.7,Her -NGC6561,OCl,18:10:30.86,-16:43:32.4,Sgr -NGC6562,G,18:05:00.91,+56:15:47.2,Dra -NGC6563,PN,18:12:02.50,-33:52:06.0,Sgr -NGC6564,Other,18:09:02.37,+17:23:40.7,Her -NGC6565,PN,18:11:52.60,-28:10:42.0,Sgr -NGC6566,G,18:07:00.67,+52:15:36.4,Dra -NGC6567,PN,18:13:45.20,-19:04:33.0,Sgr -NGC6568,OCl,18:12:44.25,-21:37:40.9,Sgr -NGC6569,GCl,18:13:38.67,-31:49:39.6,Sgr -NGC6570,G,18:11:07.30,+14:05:35.1,Oph -NGC6571,G,18:10:49.36,+21:14:18.6,Her -NGC6572,PN,18:12:06.21,+06:51:13.4,Oph -NGC6573,*Ass,18:14:22.98,-22:10:27.7,Sgr -NGC6574,G,18:11:51.23,+14:58:54.4,Her -NGC6575,G,18:10:57.49,+31:06:58.3,Her -NGC6576,G,18:11:48.02,+21:25:41.7,Her -NGC6577,G,18:12:01.23,+21:27:48.2,Her -NGC6578,PN,18:16:16.50,-20:27:03.3,Sgr -NGC6579,G,18:12:31.81,+21:25:14.5,Her -NGC6580,G,18:12:33.70,+21:25:34.1,Her -NGC6581,G,18:12:18.42,+25:39:44.5,Her -NGC6582,GPair,18:11:03.50,+49:54:38.0,Her -NGC6582 NED01,G,18:11:01.87,+49:54:42.8,Her -NGC6582 NED02,G,18:11:05.16,+49:54:33.2,Her -NGC6583,OCl,18:15:48.93,-22:08:15.5,Sgr -NGC6584,GCl,18:18:37.65,-52:12:54.6,Tel -NGC6585,G,18:12:21.79,+39:37:58.8,Her -NGC6586,G,18:13:38.50,+21:05:24.3,Her -NGC6587,G,18:13:50.89,+18:49:30.7,Her -NGC6588,Other,18:20:58.86,-63:48:35.9,Pav -NGC6589,Neb,18:16:55.37,-19:46:37.5,Sgr -NGC6590,RfN,18:17:04.99,-19:51:57.8,Sgr -NGC6591,G,18:14:03.90,+21:03:50.0,Her -NGC6592,G,18:09:50.68,+61:25:19.1,Dra -NGC6593,G,18:14:03.55,+22:17:02.1,Her -NGC6594,G,18:10:05.52,+61:08:00.5,Dra -NGC6595,Dup,18:17:04.99,-19:51:57.8,Sgr -NGC6596,OCl,18:17:33.73,-16:39:01.6,Sgr -NGC6597,G,18:11:13.45,+61:10:50.4,Dra -NGC6598,G,18:08:56.00,+69:04:04.4,Dra -NGC6599,G,18:15:42.98,+24:54:44.6,Her -NGC6600,Dup,18:15:42.98,+24:54:44.6,Her -NGC6601,G,18:11:44.35,+61:27:11.9,Dra -NGC6602,G,18:16:34.29,+25:02:38.5,Her -NGC6603,OCl,18:18:26.97,-18:24:21.8,Sgr -NGC6604,OCl,18:18:02.96,-12:14:35.2,Se2 -NGC6605,OCl,18:16:21.68,-15:00:54.7,Se2 -NGC6606,G,18:14:41.59,+43:16:07.1,Lyr -NGC6607,G,18:12:14.74,+61:19:58.5,Dra -NGC6608,G,18:12:28.88,+61:17:53.4,Dra -NGC6609,G,18:12:33.57,+61:19:54.9,Dra -NGC6610,Dup,18:11:51.23,+14:58:54.4,Her -NGC6611,Neb,18:18:48.17,-13:48:26.0,Se2 -NGC6612,G,18:16:10.85,+36:04:42.8,Lyr -NGC6613,OCl,18:19:58.49,-17:06:07.1,Sgr -NGC6614,G,18:25:07.21,-63:14:54.1,Pav -NGC6615,G,18:18:33.47,+13:15:54.1,Oph -NGC6616,G,18:17:41.08,+22:14:18.4,Her -NGC6617,G,18:14:02.52,+61:19:10.4,Dra -NGC6618,Neb,18:20:47.11,-16:10:17.5,Sgr -NGC6619,G,18:18:55.54,+23:39:20.2,Her -NGC6620,PN,18:22:54.15,-26:49:18.1,Sgr -NGC6621,G,18:12:55.31,+68:21:48.4,Dra -NGC6622,G,18:12:59.80,+68:21:14.0,Dra -NGC6623,GPair,18:19:42.90,+23:42:20.0,Her -NGC6623 NED01,G,18:19:42.82,+23:42:06.4,Her -NGC6623 NED02,G,18:19:42.86,+23:42:34.4,Her -NGC6624,GCl,18:23:40.57,-30:21:40.6,Sgr -NGC6625,OCl,18:22:47.30,-11:57:18.0,Sct -NGC6626,GCl,18:24:32.89,-24:52:11.4,Sgr -NGC6627,G,18:22:38.92,+15:41:52.8,Her -NGC6628,G,18:22:21.91,+23:28:42.8,Her -NGC6629,PN,18:25:42.46,-23:12:10.4,Sgr -NGC6630,G,18:32:35.14,-63:17:35.7,Pav -NGC6631,OCl,18:27:11.36,-12:01:52.4,Sct -NGC6632,G,18:25:03.09,+27:32:07.1,Her -NGC6633,OCl,18:27:15.23,+06:30:29.6,Oph -NGC6634,Dup,18:31:23.23,-32:20:52.7,Sgr -NGC6635,G,18:27:37.11,+14:49:08.6,Her -NGC6636,GPair,18:22:03.50,+66:37:12.8,Dra -NGC6636 NED01,G,18:22:02.87,+66:36:59.6,Dra -NGC6636 NED02,G,18:22:05.11,+66:37:19.2,Dra -NGC6637,GCl,18:31:23.23,-32:20:52.7,Sgr -NGC6638,GCl,18:30:56.25,-25:29:47.1,Sgr -NGC6639,OCl,18:30:59.30,-13:09:21.0,Sct -NGC6640,G,18:28:08.27,+34:18:09.6,Lyr -NGC6641,G,18:28:57.37,+22:54:10.8,Her -NGC6642,GCl,18:31:54.22,-23:28:34.1,Sgr -NGC6643,G,18:19:46.41,+74:34:06.1,Dra -NGC6644,PN,18:32:34.73,-25:07:45.7,Sgr -NGC6645,OCl,18:32:37.90,-16:53:02.0,Sgr -NGC6646,G,18:29:38.74,+39:51:54.5,Lyr -NGC6647,OCl,18:32:49.34,-17:13:43.2,Sgr -NGC6648,Other,18:25:37.80,+64:58:34.2,Dra -NGC6649,OCl,18:33:27.96,-10:24:10.1,Sct -NGC6650,G,18:25:27.99,+68:00:21.0,Dra -NGC6651,G,18:24:19.71,+71:36:06.9,Dra -NGC6652,GCl,18:35:45.75,-32:59:25.1,Sgr -NGC6653,G,18:44:38.37,-73:15:48.3,Pav -NGC6654,G,18:24:07.57,+73:10:59.6,Dra -NGC6654A,G,18:39:24.94,+73:34:47.1,Dra -NGC6655,**,18:34:30.90,-05:55:15.1,Sct -NGC6656,GCl,18:36:24.20,-23:54:12.3,Sgr -NGC6657,G,18:33:01.46,+34:03:37.7,Lyr -NGC6658,G,18:33:55.65,+22:53:17.8,Her -NGC6659,OCl,18:33:59.93,+23:35:41.6,Her -NGC6660,G,18:34:36.67,+22:54:34.8,Her -NGC6661,Dup,18:34:36.67,+22:54:34.8,Her -NGC6662,G,18:34:11.18,+32:03:52.5,Lyr -NGC6663,G,18:33:33.68,+40:02:56.2,Lyr -NGC6664,OCl,18:36:33.35,-08:13:14.7,Sct -NGC6665,G,18:34:30.01,+30:43:14.0,Lyr -NGC6666,Other,18:34:44.93,+33:35:15.5,Lyr -NGC6667,G,18:30:39.79,+67:59:13.3,Dra -NGC6668,Dup,18:30:39.79,+67:59:13.3,Dra -NGC6669,Other,18:37:15.09,+22:11:44.6,Her -NGC6670,GTrpl,18:33:35.45,+59:53:19.7,Dra -NGC6670A,G,18:33:37.72,+59:53:22.8,Dra -NGC6670B,G,18:33:34.09,+59:53:17.6,Dra -NGC6670 NED03,G,18:33:39.03,+59:53:17.3,Dra -NGC6671,G,18:37:26.16,+26:25:01.9,Lyr -NGC6672,**,18:36:14.39,+42:56:51.5,Lyr -NGC6673,G,18:45:06.32,-62:17:50.0,Pav -NGC6674,G,18:38:33.87,+25:22:30.5,Her -NGC6675,G,18:37:26.48,+40:03:27.8,Lyr -NGC6676,G,18:33:09.96,+66:57:32.6,Dra -NGC6677,G,18:33:36.13,+67:06:38.7,Dra -NGC6678,Dup,18:30:39.79,+67:59:13.3,Dra -NGC6679,G,18:33:30.50,+67:08:14.3,Dra -NGC6680,G,18:39:43.97,+22:18:59.1,Her -NGC6681,GCl,18:43:12.64,-32:17:30.8,Sgr -NGC6682,OCl,18:39:37.36,-04:48:49.3,Sct -NGC6683,OCl,18:42:13.97,-06:12:44.1,Sct -NGC6684,G,18:48:57.88,-65:10:24.4,Pav -NGC6684A,G,18:52:22.81,-64:49:53.3,Pav -NGC6685,G,18:39:58.64,+39:58:54.4,Lyr -NGC6686,G,18:40:07.02,+40:08:15.5,Lyr -NGC6687,G,18:37:22.11,+59:38:34.7,Dra -NGC6688,G,18:40:40.13,+36:17:22.7,Lyr -NGC6689,G,18:34:50.25,+70:31:26.1,Dra -NGC6690,Dup,18:34:50.25,+70:31:26.1,Dra -NGC6691,G,18:39:12.25,+55:38:30.5,Dra -NGC6692,GPair,18:41:41.50,+34:50:37.0,Lyr -NGC6693,Dup,18:41:41.50,+34:50:37.0,Lyr -NGC6694,OCl,18:45:18.66,-09:23:01.0,Sct -NGC6695,G,18:42:42.74,+40:22:00.8,Lyr -NGC6696,G,18:40:05.04,+59:20:02.3,Dra -NGC6697,G,18:45:14.96,+25:30:45.3,Her -NGC6698,OCl,18:48:05.01,-25:28:37.8,Sgr -NGC6699,G,18:52:02.04,-57:19:14.7,Pav -NGC6700,G,18:46:04.39,+32:16:46.6,Lyr -NGC6701,G,18:43:12.46,+60:39:12.0,Dra -NGC6702,G,18:46:57.58,+45:42:20.4,Lyr -NGC6703,G,18:47:18.83,+45:33:02.3,Lyr -NGC6704,OCl,18:50:45.77,-05:12:19.5,Sct -NGC6705,OCl,18:51:05.99,-06:16:12.1,Sct -NGC6706,G,18:56:51.04,-63:09:58.4,Pav -NGC6707,G,18:55:22.06,-53:49:06.4,Tel -NGC6708,G,18:55:35.64,-53:43:24.4,Tel -NGC6709,OCl,18:51:18.94,+10:19:07.5,Aql -NGC6710,G,18:50:34.15,+26:50:18.2,Lyr -NGC6711,G,18:49:00.88,+47:39:29.2,Dra -NGC6712,GCl,18:53:04.89,-08:42:19.7,Sct -NGC6713,G,18:50:44.56,+33:57:35.2,Lyr -NGC6714,*Ass,18:46:50.00,+66:44:42.0,Dra -NGC6715,GCl,18:55:03.27,-30:28:42.6,Sgr -NGC6716,OCl,18:54:34.37,-19:54:03.9,Sgr -NGC6717,GCl,18:55:06.04,-22:42:05.8,Sgr -NGC6718,G,19:01:28.63,-66:06:36.7,Pav -NGC6719,G,19:03:07.49,-68:35:18.1,Pav -NGC6720,PN,18:53:35.01,+33:01:42.9,Lyr -NGC6721,G,19:00:50.81,-57:45:34.0,Pav -NGC6722,G,19:03:40.37,-64:53:40.1,Pav -NGC6723,GCl,18:59:33.15,-36:37:53.3,Sgr -NGC6724,OCl,18:56:46.88,+10:25:42.8,Aql -NGC6725,G,19:01:56.62,-53:51:47.1,Tel -NGC6726,RfN,19:01:39.30,-36:53:28.7,CrA -NGC6727,RfN,19:01:42.27,-36:52:34.5,CrA -NGC6728,OCl,18:58:45.04,-08:57:57.6,Sct -NGC6729,Neb,19:01:55.40,-36:57:27.5,CrA -NGC6730,G,19:07:33.65,-68:54:46.1,Pav -NGC6731,**,18:57:13.47,+43:04:36.4,Lyr -NGC6732,G,18:56:24.02,+52:22:39.4,Dra -NGC6733,G,19:06:10.63,-62:11:48.9,Pav -NGC6734,G,19:07:14.33,-65:27:42.7,Pav -NGC6735,OCl,19:00:37.36,-00:28:31.4,Aql -NGC6736,G,19:07:29.25,-65:25:43.0,Pav -NGC6737,OCl,19:02:17.47,-18:32:49.1,Sgr -NGC6738,*Ass,19:01:21.56,+11:36:56.2,Aql -NGC6739,G,19:07:48.76,-61:22:05.2,Pav -NGC6740,G,19:00:50.51,+28:46:16.4,Lyr -NGC6741,PN,19:02:37.00,-00:26:57.8,Aql -NGC6742,PN,18:59:19.90,+48:27:55.0,Dra -NGC6743,OCl,19:01:20.67,+29:16:38.9,Lyr -NGC6744,G,19:09:46.10,-63:51:27.1,Pav -NGC6744A,G,19:08:43.76,-63:43:49.8,Pav -NGC6745,GTrpl,19:01:41.70,+40:45:11.0,Lyr -NGC6745 NED01,G,19:01:41.44,+40:44:52.3,Lyr -NGC6745 NED02,G,19:01:41.73,+40:45:35.9,Lyr -NGC6745 NED03,G,19:01:41.98,+40:45:04.9,Lyr -NGC6746,G,19:10:22.01,-61:58:12.6,Pav -NGC6747,G,18:55:21.65,+72:46:17.5,Dra -NGC6748,Dup,19:05:55.55,-05:59:32.3,Aql -NGC6749,GCl,19:05:15.60,+01:54:02.2,Aql -NGC6750,G,19:00:36.11,+59:10:00.3,Dra -NGC6751,PN,19:05:55.55,-05:59:32.3,Aql -NGC6752,GCl,19:10:51.78,-59:58:54.7,Pav -NGC6753,G,19:11:23.64,-57:02:58.4,Pav -NGC6754,G,19:11:25.72,-50:38:30.5,Tel -NGC6755,OCl,19:07:49.05,+04:15:59.1,Aql -NGC6756,OCl,19:08:42.57,+04:42:20.8,Aql -NGC6757,G,19:05:03.38,+55:42:54.6,Dra -NGC6758,G,19:13:52.34,-56:18:35.8,Tel -NGC6759,G,19:06:56.84,+50:20:39.2,Dra -NGC6760,GCl,19:11:12.06,+01:01:49.7,Aql -NGC6761,G,19:15:04.80,-50:39:24.6,Tel -NGC6762,G,19:05:37.08,+63:56:02.5,Dra -NGC6763,Dup,19:05:37.08,+63:56:02.5,Dra -NGC6764,G,19:08:16.37,+50:55:59.6,Cyg -NGC6765,PN,19:11:06.50,+30:32:44.0,Lyr -NGC6766,Dup,20:10:23.70,+46:27:39.0,Cyg -NGC6767,**,19:11:33.91,+37:43:31.6,Lyr -NGC6768,G,19:16:32.60,-40:12:33.0,CrA -NGC6769,G,19:18:22.68,-60:30:03.9,Pav -NGC6770,G,19:18:37.32,-60:29:47.3,Pav -NGC6771,G,19:18:39.51,-60:32:45.6,Pav -NGC6772,PN,19:14:36.30,-02:42:24.5,Aql -NGC6773,OCl,19:15:08.45,+04:51:23.6,Aql -NGC6774,OCl,19:16:16.30,-16:15:38.5,Sgr -NGC6775,OCl,19:16:42.86,-00:56:00.1,Aql -NGC6776,G,19:25:19.15,-63:51:36.6,Pav -NGC6776A,G,19:25:05.38,-63:41:00.4,Pav -NGC6777,Dup,19:10:51.78,-59:58:54.7,Pav -NGC6778,PN,19:18:24.85,-01:35:46.9,Aql -NGC6779,GCl,19:16:35.51,+30:11:04.2,Lyr -NGC6780,G,19:22:50.88,-55:46:33.0,Tel -NGC6781,PN,19:18:28.26,+06:32:23.0,Aql -NGC6782,G,19:23:57.90,-59:55:20.9,Pav -NGC6783,G,19:16:47.62,+46:01:02.0,Cyg -NGC6784,G,19:26:35.99,-65:37:03.7,Pav -NGC6784A,G,19:26:31.18,-65:37:34.0,Pav -NGC6785,Dup,19:18:24.85,-01:35:46.9,Aql -NGC6786,G,19:10:53.91,+73:24:36.6,Dra -NGC6787,G,19:16:10.58,+60:25:03.1,Dra -NGC6788,G,19:26:49.75,-54:57:04.7,Tel -NGC6789,G,19:16:42.16,+63:58:17.3,Dra -NGC6790,PN,19:22:56.90,+01:30:48.0,Aql -NGC6791,OCl,19:20:53.22,+37:46:18.8,Lyr -NGC6792,G,19:20:57.41,+43:07:57.0,Lyr -NGC6793,OCl,19:23:14.39,+22:08:27.7,Vul -NGC6794,G,19:28:03.88,-38:55:07.5,Sgr -NGC6795,OCl,19:26:22.02,+03:30:51.7,Aql -NGC6796,G,19:21:30.87,+61:08:41.6,Dra -NGC6797,Other,19:29:00.75,-25:39:59.6,Sgr -NGC6798,G,19:24:03.17,+53:37:29.2,Cyg -NGC6799,G,19:32:16.53,-55:54:28.5,Tel -NGC6800,OCl,19:27:07.68,+25:08:25.7,Vul -NGC6801,G,19:27:35.81,+54:22:22.4,Cyg -NGC6802,OCl,19:30:35.04,+20:15:39.5,Vul -NGC6803,PN,19:31:16.45,+10:03:21.7,Aql -NGC6804,PN,19:31:35.39,+09:13:30.6,Aql -NGC6805,G,19:36:45.71,-37:33:15.7,Sgr -NGC6806,G,19:37:05.05,-42:17:46.6,Sgr -NGC6807,PN,19:34:33.50,+05:41:03.0,Aql -NGC6808,G,19:43:54.00,-70:38:00.2,Pav -NGC6809,GCl,19:39:59.40,-30:57:43.5,Sgr -NGC6810,G,19:43:34.25,-58:39:20.1,Pav -NGC6811,OCl,19:37:17.91,+46:23:19.8,Cyg -NGC6812,G,19:45:24.22,-55:20:48.5,Tel -NGC6813,PN,19:40:22.44,+27:18:34.4,Vul -NGC6814,G,19:42:40.64,-10:19:24.6,Aql -NGC6815,*Ass,19:40:44.31,+26:45:32.4,Vul -NGC6816,G,19:44:02.23,-28:24:03.0,Sgr -NGC6817,GPair,19:37:22.40,+62:22:59.0,Dra -NGC6817 NED01,G,19:37:21.24,+62:22:57.7,Dra -NGC6817 NED02,G,19:37:23.38,+62:23:01.1,Dra -NGC6818,PN,19:43:57.73,-14:09:11.4,Sgr -NGC6819,OCl,19:41:18.09,+40:11:12.3,Cyg -NGC6820,Neb,19:42:28.02,+23:05:17.1,Vul -NGC6821,G,19:44:24.06,-06:50:00.4,Aql -NGC6822,G,19:44:57.74,-14:48:12.4,Sgr -NGC6823,Cl+N,19:43:09.89,+23:17:59.8,Vul -NGC6824,G,19:43:40.70,+56:06:34.1,Cyg -NGC6825,GPair,19:41:54.80,+64:04:23.0,Dra -NGC6826,PN,19:44:48.11,+50:31:30.1,Cyg -NGC6827,OCl,19:48:53.44,+21:12:54.2,Vul -NGC6828,OCl,19:50:17.61,+07:54:09.2,Aql -NGC6829,G,19:47:07.56,+59:54:25.5,Dra -NGC6830,OCl,19:50:59.58,+23:06:00.5,Vul -NGC6831,G,19:47:57.30,+59:53:33.1,Dra -NGC6832,OCl,19:48:15.26,+59:25:16.2,Cyg -NGC6833,PN,19:49:46.60,+48:57:40.0,Cyg -NGC6834,OCl,19:52:12.56,+29:24:29.4,Cyg -NGC6835,G,19:54:32.95,-12:34:03.3,Sgr -NGC6836,G,19:54:40.06,-12:41:16.6,Sgr -NGC6837,OCl,19:53:08.63,+11:41:56.4,Aql -NGC6838,GCl,19:53:46.11,+18:46:42.2,Sge -NGC6839,Other,19:54:00.00,+18:00:12.0,Sge -NGC6840,OCl,19:55:16.25,+12:06:52.6,Aql -NGC6841,G,19:57:49.06,-31:48:38.5,Sgr -NGC6842,PN,19:55:02.26,+29:17:21.0,Vul -NGC6843,OCl,19:56:06.22,+12:09:49.8,Aql -NGC6844,G,20:02:50.13,-65:13:47.5,Pav -NGC6845,GGroup,20:00:58.28,-47:04:11.9,Tel -NGC6845A,G,20:00:58.42,-47:04:12.9,Tel -NGC6845B,G,20:01:05.30,-47:03:32.7,Tel -NGC6845C,G,20:00:56.81,-47:05:03.2,Tel -NGC6845D,G,20:00:53.59,-47:05:42.8,Tel -NGC6846,OCl,19:56:28.12,+32:20:58.9,Cyg -NGC6847,Cl+N,19:56:37.82,+30:12:46.5,Cyg -NGC6848,G,20:02:47.25,-56:05:25.1,Tel -NGC6849,G,20:06:15.62,-40:11:53.9,Sgr -NGC6850,G,20:03:30.10,-54:50:41.2,Tel -NGC6851,G,20:03:34.37,-48:17:04.2,Tel -NGC6851A,G,20:05:48.51,-47:58:41.7,Tel -NGC6851B,G,20:05:39.94,-47:58:44.6,Tel -NGC6852,PN,20:00:39.16,+01:43:41.2,Aql -NGC6853,PN,19:59:36.38,+22:43:15.7,Vul -NGC6854,G,20:05:38.80,-54:22:32.2,Tel -NGC6855,G,20:06:49.91,-56:23:23.7,Tel -NGC6856,OCl,19:59:17.13,+56:07:51.2,Cyg -NGC6857,HII,20:01:48.13,+33:31:33.3,Cyg -NGC6858,OCl,20:02:59.39,+11:15:33.9,Aql -NGC6859,Other,20:03:49.51,+00:26:40.7,Aql -NGC6860,G,20:08:46.89,-61:06:00.7,Pav -NGC6861,G,20:07:19.48,-48:22:12.8,Tel -NGC6861B,G,20:06:05.43,-48:28:28.0,Tel -NGC6861C,G,20:06:41.08,-48:38:59.2,Tel -NGC6861D,G,20:08:19.48,-48:12:41.1,Tel -NGC6861E,G,20:11:01.52,-48:41:26.1,Tel -NGC6861F,G,20:11:11.80,-48:16:33.0,Tel -NGC6862,G,20:08:54.58,-56:23:30.3,Tel -NGC6863,Other,20:05:07.32,-03:33:18.5,Aql -NGC6864,GCl,20:06:04.84,-21:55:20.0,Sgr -NGC6865,G,20:05:56.48,-09:02:27.3,Aql -NGC6866,OCl,20:03:55.18,+44:09:32.8,Cyg -NGC6867,G,20:10:29.66,-54:46:59.7,Tel -NGC6868,G,20:09:54.07,-48:22:46.4,Tel -NGC6869,G,20:00:42.41,+66:13:39.1,Dra -NGC6870,G,20:10:10.86,-48:17:13.5,Tel -NGC6871,OCl,20:05:59.44,+35:46:38.1,Cyg -NGC6872,G,20:16:56.56,-70:46:04.6,Pav -NGC6873,OCl,20:07:13.89,+21:06:08.1,Sge -NGC6874,*Ass,20:07:33.08,+38:14:46.0,Cyg -NGC6875,G,20:13:12.47,-46:09:41.9,Tel -NGC6875A,G,20:11:55.86,-46:08:38.7,Tel -NGC6876,G,20:18:19.15,-70:51:31.7,Pav -NGC6876A,G,20:11:16.85,-71:00:46.6,Pav -NGC6877,G,20:18:36.20,-70:51:11.0,Pav -NGC6878,G,20:13:53.24,-44:31:36.3,Sgr -NGC6878A,G,20:13:36.00,-44:48:58.2,Sgr -NGC6879,PN,20:10:26.60,+16:55:22.1,Sge -NGC6880,G,20:19:29.63,-70:51:35.5,Pav -NGC6881,PN,20:10:52.50,+37:24:41.0,Cyg -NGC6882,OCl,20:11:55.86,+26:29:19.7,Vul -NGC6883,OCl,20:11:19.75,+35:49:55.9,Cyg -NGC6884,PN,20:10:23.70,+46:27:39.0,Cyg -NGC6885,Dup,20:11:55.86,+26:29:19.7,Vul -NGC6886,PN,20:12:42.90,+19:59:23.0,Sge -NGC6887,G,20:17:17.29,-52:47:48.3,Tel -NGC6888,HII,20:12:06.55,+38:21:17.8,Cyg -NGC6889,G,20:18:53.23,-53:57:25.5,Tel -NGC6890,G,20:18:18.10,-44:48:24.2,Sgr -NGC6891,PN,20:15:08.84,+12:42:15.7,Del -NGC6892,Other,20:16:57.24,+18:01:10.8,Sge -NGC6893,G,20:20:49.64,-48:14:20.7,Tel -NGC6894,PN,20:16:24.05,+30:33:54.3,Cyg -NGC6895,OCl,20:16:32.36,+50:14:25.7,Cyg -NGC6896,**,20:18:03.58,+30:38:24.2,Cyg -NGC6897,G,20:21:01.27,-12:15:16.9,Cap -NGC6898,G,20:21:08.03,-12:21:32.0,Cap -NGC6899,G,20:24:22.24,-50:26:02.2,Tel -NGC6900,G,20:21:35.11,-02:34:09.2,Aql -NGC6901,G,20:22:21.51,+06:25:47.5,Aql -NGC6902,G,20:24:28.14,-43:39:12.7,Sgr -NGC6902A,G,20:22:59.76,-44:16:17.5,Sgr -NGC6902B,G,20:23:07.05,-43:52:07.0,Sgr -NGC6903,G,20:23:44.86,-19:19:31.5,Cap -NGC6904,OCl,20:21:48.15,+25:44:29.4,Vul -NGC6905,PN,20:22:22.99,+20:06:16.3,Del -NGC6906,G,20:23:33.91,+06:26:37.2,Aql -NGC6907,G,20:25:06.63,-24:48:33.0,Cap -NGC6908,G,20:25:08.97,-24:48:04.1,Cap -NGC6909,G,20:27:38.89,-47:01:37.3,Tel -NGC6910,OCl,20:23:12.05,+40:46:43.0,Cyg -NGC6911,G,20:19:38.33,+66:43:42.0,Dra -NGC6912,G,20:26:52.08,-18:37:02.2,Cap -NGC6913,OCl,20:23:57.77,+38:30:27.6,Cyg -NGC6914,RfN,20:24:43.30,+42:28:57.5,Cyg -NGC6915,G,20:27:46.06,-03:04:37.4,Aql -NGC6916,G,20:23:33.08,+58:20:38.6,Cyg -NGC6917,G,20:27:28.36,+08:05:53.1,Del -NGC6918,G,20:30:47.12,-47:28:25.4,Ind -NGC6919,G,20:31:38.12,-44:12:59.1,Mic -NGC6920,G,20:43:57.40,-80:00:03.0,Oct -NGC6921,G,20:28:28.86,+25:43:24.3,Vul -NGC6922,G,20:29:52.90,-02:11:28.3,Aql -NGC6923,G,20:31:39.07,-30:49:54.8,Mic -NGC6924,G,20:33:19.23,-25:28:28.0,Cap -NGC6925,G,20:34:20.57,-31:58:51.2,Mic -NGC6926,G,20:33:06.11,-02:01:39.0,Aql -NGC6927,G,20:32:38.21,+09:54:59.0,Del -NGC6927A,G,20:32:36.69,+09:53:02.1,Del -NGC6928,G,20:32:50.22,+09:55:35.1,Del -NGC6929,G,20:33:21.68,-02:02:13.9,Aql -NGC6930,G,20:32:58.80,+09:52:28.0,Del -NGC6931,G,20:33:41.35,-11:22:07.9,Cap -NGC6932,G,20:42:08.58,-73:37:09.7,Pav -NGC6933,*,20:33:38.18,+07:23:14.4,Del -NGC6934,GCl,20:34:11.49,+07:24:14.8,Del -NGC6935,G,20:38:20.22,-52:06:37.6,Ind -NGC6936,G,20:35:56.30,-25:16:47.9,Cap -NGC6937,G,20:38:45.83,-52:08:35.7,Ind -NGC6938,OCl,20:34:42.49,+22:12:52.5,Vul -NGC6939,OCl,20:31:30.13,+60:39:43.5,Cep -NGC6940,OCl,20:34:26.69,+28:16:57.8,Vul -NGC6941,G,20:36:23.47,-04:37:07.5,Aql -NGC6942,G,20:40:37.85,-54:18:11.0,Ind -NGC6943,G,20:44:33.74,-68:44:51.8,Pav -NGC6944,G,20:38:23.86,+06:59:47.2,Del -NGC6944A,G,20:38:11.31,+06:54:09.6,Del -NGC6945,G,20:39:00.62,-04:58:21.3,Aqr -NGC6946,G,20:34:52.32,+60:09:14.1,Cyg -NGC6947,G,20:41:15.12,-32:29:11.1,Mic -NGC6948,G,20:43:29.14,-53:21:24.2,Ind -NGC6949,G,20:35:06.92,+64:48:10.1,Cep -NGC6950,OCl,20:41:05.58,+16:37:20.0,Del -NGC6951,G,20:37:14.09,+66:06:20.3,Cep -NGC6952,Dup,20:37:14.09,+66:06:20.3,Cep -NGC6953,Other,20:37:46.22,+65:45:53.5,Cep -NGC6954,G,20:44:03.18,+03:12:34.0,Del -NGC6955,G,20:44:17.98,+02:35:41.4,Del -NGC6956,G,20:43:53.71,+12:30:42.9,Del -NGC6957,G,20:44:47.56,+02:34:52.4,Del -NGC6958,G,20:48:42.59,-37:59:50.7,Mic -NGC6959,G,20:47:07.24,+00:25:48.7,Aqr -NGC6960,SNR,20:45:58.18,+30:35:42.5,Cyg -NGC6961,G,20:47:10.51,+00:21:47.8,Aqr -NGC6962,G,20:47:19.06,+00:19:14.9,Aqr -NGC6963,**,20:47:19.47,+00:30:38.3,Aqr -NGC6964,G,20:47:24.30,+00:18:03.0,Aqr -NGC6965,G,20:47:20.37,+00:29:02.6,Aqr -NGC6966,**,20:47:26.76,+00:22:03.7,Aqr -NGC6967,G,20:47:34.10,+00:24:41.8,Aqr -NGC6968,G,20:48:32.48,-08:21:37.1,Aqr -NGC6969,G,20:48:27.63,+07:44:23.9,Del -NGC6970,G,20:52:09.46,-48:46:40.0,Ind -NGC6971,G,20:49:23.76,+05:59:44.1,Del -NGC6972,G,20:49:58.94,+09:53:56.9,Del -NGC6973,*,20:52:05.92,-05:53:42.7,Aqr -NGC6974,SNR,20:51:04.32,+31:49:41.2,Cyg -NGC6975,G,20:52:26.03,-05:46:20.3,Aqr -NGC6976,Dup,20:52:26.03,-05:46:20.3,Aqr -NGC6977,G,20:52:29.71,-05:44:46.0,Aqr -NGC6978,G,20:52:35.43,-05:42:40.1,Aqr -NGC6979,SNR,20:50:28.01,+32:01:33.2,Cyg -NGC6980,*,20:52:48.94,-05:50:16.4,Aqr -NGC6981,GCl,20:53:27.91,-12:32:13.4,Aqr -NGC6982,G,20:57:18.38,-51:51:44.2,Ind -NGC6983,G,20:56:43.43,-43:59:09.7,Mic -NGC6984,G,20:57:53.98,-51:52:15.0,Ind -NGC6985,G,20:45:02.98,-11:06:15.1,Aqr -NGC6985A,G,20:45:01.27,-11:06:27.6,Aqr -NGC6986,G,20:56:30.66,-18:33:59.6,Cap -NGC6987,G,20:58:10.36,-48:37:49.1,Ind -NGC6988,G,20:55:48.96,+10:30:28.3,Del -NGC6989,OCl,20:54:06.92,+45:14:21.4,Cyg -NGC6990,G,20:59:56.92,-55:33:43.1,Ind -NGC6991,OCl,20:54:52.00,+47:27:42.0,Cyg -NGC6992,SNR,20:56:19.07,+31:44:33.9,Cyg -NGC6993,G,20:53:54.05,-25:28:21.1,Cap -NGC6994,Other,20:58:55.97,-12:38:07.8,Aqr -NGC6995,SNR,20:57:10.76,+31:14:06.6,Cyg -NGC6996,*Ass,20:56:30.01,+45:28:22.9,Cyg -NGC6997,Cl+N,20:56:39.45,+44:37:53.4,Cyg -NGC6998,G,21:01:37.68,-28:01:54.9,Mic -NGC6999,G,21:01:59.54,-28:03:32.1,Mic -NGC7000,HII,20:59:17.14,+44:31:43.6,Cyg -NGC7001,G,21:01:07.75,-00:11:42.6,Aqr -NGC7002,G,21:03:44.80,-49:01:47.1,Ind -NGC7003,G,21:00:42.42,+17:48:17.6,Del -NGC7004,G,21:04:02.18,-49:06:51.3,Ind -NGC7005,Other,21:01:57.65,-12:52:57.4,Aqr -NGC7006,GCl,21:01:29.25,+16:11:15.1,Del -NGC7007,G,21:05:27.92,-52:33:07.1,Ind -NGC7008,PN,21:00:32.80,+54:32:35.5,Cyg -NGC7009,PN,21:04:10.79,-11:21:47.7,Aqr -NGC7010,G,21:04:39.50,-12:20:18.2,Aqr -NGC7011,OCl,21:01:49.38,+47:21:15.5,Cyg -NGC7012,G,21:06:45.52,-44:48:53.0,Mic -NGC7013,G,21:03:33.58,+29:53:50.9,Cyg -NGC7014,G,21:07:52.17,-47:10:44.4,Ind -NGC7015,G,21:05:37.38,+11:24:51.0,Equ -NGC7016,G,21:07:16.28,-25:28:08.2,Cap -NGC7017,GPair,21:07:20.53,-25:29:12.7,Cap -NGC7018,GTrpl,21:07:25.35,-25:25:39.9,Cap -NGC7019,G,21:06:25.73,-24:24:45.6,Cap -NGC7020,G,21:11:20.09,-64:01:31.2,Pav -NGC7021,Dup,21:11:20.09,-64:01:31.2,Pav -NGC7022,G,21:09:35.24,-49:18:13.2,Ind -NGC7023,Neb,21:01:35.62,+68:10:10.4,Cep -NGC7024,OCl,21:06:08.11,+41:29:04.5,Cyg -NGC7025,G,21:07:47.34,+16:20:09.1,Del -NGC7026,PN,21:06:18.48,+47:51:07.9,Cyg -NGC7027,PN,21:07:01.53,+42:14:11.5,Cyg -NGC7028,G,21:05:50.04,+18:28:05.5,Del -NGC7029,G,21:11:52.05,-49:17:01.4,Ind -NGC7030,G,21:11:13.33,-20:29:09.1,Cap -NGC7031,OCl,21:07:12.55,+50:52:32.0,Cyg -NGC7032,G,21:15:22.88,-68:17:16.4,Pav -NGC7033,G,21:09:36.26,+15:07:29.6,Peg -NGC7034,G,21:09:38.18,+15:09:02.4,Peg -NGC7035,GPair,21:10:46.40,-23:08:09.0,Cap -NGC7035A,G,21:10:45.46,-23:08:06.5,Cap -NGC7035B,G,21:10:47.30,-23:08:13.5,Cap -NGC7036,OCl,21:10:12.39,+15:22:34.0,Peg -NGC7037,OCl,21:10:45.72,+33:43:42.0,Cyg -NGC7038,G,21:15:07.51,-47:13:13.8,Ind -NGC7039,OCl,21:10:47.80,+45:37:18.5,Cyg -NGC7040,G,21:13:16.50,+08:51:53.9,Equ -NGC7041,G,21:16:32.38,-48:21:48.8,Ind -NGC7042,G,21:13:45.86,+13:34:29.7,Peg -NGC7043,G,21:14:04.17,+13:37:33.7,Peg -NGC7044,OCl,21:13:09.41,+42:29:46.3,Cyg -NGC7045,**,21:14:50.06,+04:30:24.4,Equ -NGC7046,G,21:14:56.04,+02:50:05.4,Equ -NGC7047,G,21:16:27.65,-00:49:35.4,Aqr -NGC7048,PN,21:14:15.20,+46:17:19.0,Cyg -NGC7049,G,21:19:00.30,-48:33:43.8,Ind -NGC7050,OCl,21:15:08.55,+36:10:30.9,Cyg -NGC7051,G,21:19:51.33,-08:46:58.6,Aqr -NGC7052,G,21:18:33.05,+26:26:49.3,Vul -NGC7053,G,21:21:07.61,+23:05:05.3,Peg -NGC7054,Dup,21:30:01.95,+26:43:04.1,Vul -NGC7055,OCl,21:19:30.11,+57:32:44.2,Cep -NGC7056,G,21:22:07.56,+18:39:56.4,Peg -NGC7057,G,21:24:58.70,-42:27:37.6,Mic -NGC7058,OCl,21:21:53.58,+50:49:08.5,Cyg -NGC7059,G,21:27:21.47,-60:00:52.5,Pav -NGC7060,G,21:25:53.58,-42:24:40.6,Mic -NGC7061,G,21:27:26.85,-49:03:48.6,Ind -NGC7062,OCl,21:23:27.48,+46:22:42.7,Cyg -NGC7063,OCl,21:24:21.70,+36:29:15.0,Cyg -NGC7064,G,21:29:02.98,-52:46:03.4,Ind -NGC7065,G,21:26:42.51,-06:59:41.7,Aqr -NGC7065A,G,21:26:57.85,-07:01:17.5,Aqr -NGC7066,GPair,21:26:14.00,+14:10:55.0,Peg -NGC7067,OCl,21:24:23.12,+48:00:33.3,Cyg -NGC7068,G,21:26:32.37,+12:11:03.5,Peg -NGC7069,G,21:28:05.87,-01:38:48.7,Aqr -NGC7070,G,21:30:25.35,-43:05:13.6,Gru -NGC7070A,G,21:31:47.29,-42:50:51.6,Gru -NGC7071,*Ass,21:26:39.33,+47:55:10.5,Cyg -NGC7072,G,21:30:36.92,-43:09:12.7,Gru -NGC7072A,G,21:30:25.68,-43:12:09.4,Gru -NGC7073,G,21:29:26.02,-11:29:17.3,Cap -NGC7074,G,21:29:38.84,+06:40:57.2,Peg -NGC7075,G,21:31:32.98,-38:37:04.5,Gru -NGC7076,PN,21:26:23.50,+62:53:33.0,Cep -NGC7077,G,21:29:59.61,+02:24:51.0,Aqr -NGC7078,GCl,21:29:58.38,+12:10:00.6,Peg -NGC7079,G,21:32:35.25,-44:04:03.2,Gru -NGC7080,G,21:30:01.95,+26:43:04.1,Vul -NGC7081,G,21:31:24.12,+02:29:28.6,Aqr -NGC7082,OCl,21:29:17.74,+47:07:34.6,Cyg -NGC7083,G,21:35:44.69,-63:54:10.2,Ind -NGC7084,OCl,21:32:33.13,+17:30:30.7,Peg -NGC7085,G,21:32:25.23,+06:34:52.5,Peg -NGC7086,OCl,21:30:27.55,+51:36:01.9,Cyg -NGC7087,G,21:34:33.47,-40:49:07.1,Gru -NGC7088,Other,21:33:22.12,-00:22:57.4,Aqr -NGC7089,GCl,21:33:27.01,-00:49:23.9,Aqr -NGC7090,G,21:36:28.86,-54:33:26.4,Ind -NGC7091,G,21:34:07.94,-36:39:13.4,Gru -NGC7092,OCl,21:31:48.32,+48:26:17.4,Cyg -NGC7093,OCl,21:34:21.70,+45:57:54.0,Cyg -NGC7094,PN,21:36:52.97,+12:47:19.1,Peg -NGC7095,G,21:52:26.44,-81:31:51.0,Oct -NGC7096,G,21:41:19.28,-63:54:31.3,Ind -NGC7096A,G,21:38:48.27,-64:21:00.7,Ind -NGC7097,G,21:40:12.91,-42:32:21.8,Gru -NGC7097A,G,21:40:37.86,-42:28:49.0,Gru -NGC7098,G,21:44:16.12,-75:06:40.8,Oct -NGC7099,GCl,21:40:22.02,-23:10:44.7,Cap -NGC7100,*,21:39:07.04,+08:57:05.5,Peg -NGC7101,G,21:39:34.61,+08:52:37.0,Peg -NGC7102,G,21:39:44.49,+06:17:10.7,Peg -NGC7103,G,21:39:51.50,-22:28:26.0,Cap -NGC7104,G,21:40:03.18,-22:25:29.3,Cap -NGC7105,G,21:41:41.35,-10:38:07.8,Cap -NGC7106,G,21:42:36.59,-52:41:58.3,Ind -NGC7107,G,21:42:26.48,-44:47:25.0,Gru -NGC7108,G,21:41:53.75,-06:42:31.8,Aqr -NGC7109,G,21:41:58.53,-34:26:45.2,PsA -NGC7110,G,21:42:12.15,-34:09:44.0,PsA -NGC7111,Dup,21:41:53.75,-06:42:31.8,Aqr -NGC7112,G,21:42:26.64,+12:34:09.3,Peg -NGC7113,Dup,21:42:26.64,+12:34:09.3,Peg -NGC7114,*,21:41:44.03,+42:50:30.5,Cyg -NGC7115,G,21:43:38.53,-25:21:05.8,PsA -NGC7116,G,21:42:40.22,+28:56:47.7,Cyg -NGC7117,G,21:45:47.04,-48:25:13.5,Gru -NGC7118,G,21:46:09.74,-48:21:13.7,Gru -NGC7119,GPair,21:46:15.50,-46:31:05.0,Gru -NGC7119A,G,21:46:16.05,-46:30:58.1,Gru -NGC7119B,G,21:46:15.18,-46:31:06.3,Gru -NGC7120,G,21:44:33.22,-06:31:23.4,Aqr -NGC7121,G,21:44:52.58,-03:37:11.0,Aqr -NGC7122,**,21:45:47.82,-08:49:46.6,Cap -NGC7123,G,21:50:46.63,-70:20:02.9,Ind -NGC7124,G,21:48:05.39,-50:33:54.8,Ind -NGC7125,G,21:49:15.98,-60:42:47.4,Ind -NGC7126,G,21:49:18.13,-60:36:33.2,Ind -NGC7127,OCl,21:43:41.99,+54:37:47.9,Cyg -NGC7128,OCl,21:43:57.81,+53:42:54.5,Cyg -NGC7129,Cl+N,21:42:59.03,+66:06:46.7,Cep -NGC7130,G,21:48:19.52,-34:57:04.5,PsA -NGC7131,G,21:47:36.11,-13:10:57.4,Cap -NGC7132,G,21:47:16.58,+10:14:28.2,Peg -NGC7133,Other,21:44:26.70,+66:10:06.3,Cep -NGC7134,Other,21:48:56.26,-12:58:23.3,Cap -NGC7135,G,21:49:46.01,-34:52:34.6,PsA -NGC7136,**,21:49:43.13,-11:47:35.9,Cap -NGC7137,G,21:48:13.04,+22:09:34.5,Peg -NGC7138,G,21:49:01.09,+12:30:51.2,Peg -NGC7139,PN,21:46:08.60,+63:47:31.0,Cep -NGC7140,G,21:52:15.31,-55:34:10.8,Ind -NGC7141,Dup,21:52:15.31,-55:34:10.8,Ind -NGC7142,OCl,21:45:09.51,+65:46:28.0,Cep -NGC7143,Other,21:48:53.67,+29:57:17.9,Cyg -NGC7144,G,21:52:42.43,-48:15:13.5,Gru -NGC7145,G,21:53:20.24,-47:52:56.8,Gru -NGC7146,G,21:51:47.37,+03:01:01.3,Peg -NGC7147,G,21:51:58.43,+03:04:18.1,Peg -NGC7148,**,21:52:08.47,+03:20:29.3,Peg -NGC7149,G,21:52:11.68,+03:18:04.1,Peg -NGC7150,Other,21:50:23.99,+49:45:21.9,Cyg -NGC7151,G,21:55:04.14,-50:39:28.4,Ind -NGC7152,G,21:53:59.04,-29:17:20.7,PsA -NGC7153,G,21:54:35.41,-29:03:49.0,PsA -NGC7154,G,21:55:21.04,-34:48:50.9,PsA -NGC7155,G,21:56:09.73,-49:31:19.0,Ind -NGC7156,G,21:54:33.63,+02:56:34.9,Peg -NGC7157,G,21:56:56.70,-25:21:01.9,PsA -NGC7158,Other,21:57:28.12,-11:35:33.1,Cap -NGC7159,G,21:56:25.61,+13:33:45.5,Peg -NGC7160,OCl,21:53:40.27,+62:36:11.9,Cep -NGC7161,Other,21:56:57.89,+02:54:58.4,Peg -NGC7162,G,21:59:39.13,-43:18:21.5,Gru -NGC7162A,G,22:00:35.75,-43:08:30.2,Gru -NGC7163,G,21:59:20.44,-31:52:59.4,PsA -NGC7164,G,21:56:23.64,+01:21:50.2,Aqr -NGC7165,G,21:59:26.10,-16:30:44.4,Aqr -NGC7166,G,22:00:32.92,-43:23:23.0,Gru -NGC7167,G,22:00:30.64,-24:37:57.4,Aqr -NGC7168,G,22:02:07.40,-51:44:35.1,Ind -NGC7169,G,22:02:48.64,-47:41:52.2,Gru -NGC7170,G,22:01:26.26,-05:25:58.0,Aqr -NGC7171,G,22:01:02.01,-13:16:11.1,Aqr -NGC7172,G,22:02:01.89,-31:52:10.8,PsA -NGC7173,G,22:02:03.19,-31:58:25.3,PsA -NGC7174,G,22:02:06.45,-31:59:34.7,PsA -NGC7175,OCl,21:58:39.83,+54:48:22.9,Cyg -NGC7176,G,22:02:08.44,-31:59:23.1,PsA -NGC7177,G,22:00:41.24,+17:44:17.0,Peg -NGC7178,G,22:02:25.20,-35:47:25.3,PsA -NGC7179,G,22:04:49.32,-64:02:48.6,Ind -NGC7180,G,22:02:18.46,-20:32:51.9,Aqr -NGC7181,G,22:01:43.49,-01:57:38.0,Aqr -NGC7182,G,22:01:51.66,-02:11:47.7,Aqr -NGC7183,G,22:02:21.62,-18:54:59.4,Aqr -NGC7184,G,22:02:39.82,-20:48:46.2,Aqr -NGC7185,G,22:02:56.75,-20:28:16.6,Aqr -NGC7186,Other,22:01:05.22,+35:04:43.8,Peg -NGC7187,G,22:02:44.56,-32:48:07.8,PsA -NGC7188,G,22:03:29.00,-20:19:04.7,Aqr -NGC7189,G,22:03:16.00,+00:34:16.0,Aqr -NGC7190,G,22:03:06.68,+11:11:57.5,Peg -NGC7191,G,22:06:51.79,-64:38:04.4,Ind -NGC7192,G,22:06:50.16,-64:18:58.5,Ind -NGC7193,OCl,22:03:01.56,+10:48:13.8,Peg -NGC7194,G,22:03:30.94,+12:38:12.4,Peg -NGC7195,G,22:03:30.28,+12:39:39.0,Peg -NGC7196,G,22:05:54.81,-50:07:09.6,Ind -NGC7197,G,22:02:58.05,+41:03:32.4,Lac -NGC7198,G,22:05:14.24,-00:38:53.8,Aqr -NGC7199,G,22:08:29.84,-64:42:22.1,Ind -NGC7200,G,22:07:09.52,-49:59:43.7,Ind -NGC7201,G,22:06:31.93,-31:15:46.8,PsA -NGC7202,*,22:06:43.24,-31:13:04.4,PsA -NGC7203,G,22:06:43.88,-31:09:44.7,PsA -NGC7204,GPair,22:06:54.20,-31:03:05.0,PsA -NGC7204A,G,22:06:54.00,-31:03:02.2,PsA -NGC7204B,G,22:06:55.26,-31:03:11.1,PsA -NGC7205,G,22:08:34.29,-57:26:33.3,Tuc -NGC7205A,G,22:07:32.01,-57:27:50.5,Ind -NGC7206,G,22:05:40.89,+16:47:06.9,Peg -NGC7207,G,22:05:45.68,+16:46:03.9,Peg -NGC7208,G,22:08:24.42,-29:03:04.2,PsA -NGC7209,OCl,22:05:07.84,+46:29:00.7,Lac -NGC7210,Dup,23:06:50.54,+28:10:44.5,Peg -NGC7211,G,22:06:21.87,-08:05:23.9,Aqr -NGC7212,GTrpl,22:07:01.30,+10:13:52.0,Peg -NGC7213,G,22:09:16.31,-47:09:59.8,Gru -NGC7214,G,22:09:07.68,-27:48:34.1,PsA -NGC7215,G,22:08:34.48,+00:30:42.1,Aqr -NGC7216,G,22:12:35.84,-68:39:42.7,Ind -NGC7217,G,22:07:52.39,+31:21:33.6,Peg -NGC7218,G,22:10:11.71,-16:39:39.6,Aqr -NGC7219,G,22:13:09.02,-64:50:56.3,Tuc -NGC7220,G,22:11:31.00,-22:57:10.3,Aqr -NGC7221,G,22:11:15.25,-30:33:47.4,PsA -NGC7222,G,22:10:51.76,+02:06:20.9,Aqr -NGC7223,G,22:10:09.16,+41:01:02.2,Lac -NGC7224,G,22:11:35.38,+25:51:52.2,Peg -NGC7225,G,22:13:08.07,-26:08:54.0,PsA -NGC7226,OCl,22:10:26.89,+55:23:54.9,Cep -NGC7227,G,22:11:31.33,+38:43:16.9,Lac -NGC7228,G,22:11:48.60,+38:41:57.0,Lac -NGC7229,G,22:14:03.22,-29:22:57.9,PsA -NGC7230,G,22:14:13.19,-17:04:26.6,Aqr -NGC7231,G,22:12:30.12,+45:19:42.5,Lac -NGC7232,G,22:15:38.00,-45:51:00.3,Gru -NGC7232A,G,22:13:41.44,-45:53:37.4,Gru -NGC7232B,G,22:15:52.45,-45:46:50.3,Gru -NGC7233,G,22:15:48.98,-45:50:47.2,Gru -NGC7234,OCl,22:12:25.02,+57:16:16.8,Cep -NGC7235,Dup,22:12:25.02,+57:16:16.8,Cep -NGC7236,G,22:14:44.99,+13:50:47.5,Peg -NGC7237,G,22:14:46.88,+13:50:27.1,Peg -NGC7238,Other,22:15:20.51,+22:31:09.2,Peg -NGC7239,G,22:15:01.40,-05:03:12.0,Aqr -NGC7240,G,22:15:22.56,+37:16:50.4,Lac -NGC7241,G,22:15:49.87,+19:13:56.5,Peg -NGC7242,GPair,22:15:40.50,+37:18:02.0,Lac -NGC7242 NED01,G,22:15:39.49,+37:17:55.4,Lac -NGC7242 NED02,G,22:15:41.57,+37:18:10.0,Lac -NGC7243,OCl,22:15:08.58,+49:53:51.0,Lac -NGC7244,G,22:16:26.83,+16:28:17.2,Peg -NGC7245,OCl,22:15:11.51,+54:20:33.2,Lac -NGC7246,G,22:17:42.68,-15:34:16.6,Aqr -NGC7247,G,22:17:41.25,-23:43:51.7,Aqr -NGC7248,G,22:16:53.94,+40:30:08.4,Lac -NGC7249,G,22:20:31.00,-55:07:29.5,Gru -NGC7250,G,22:18:17.80,+40:33:44.6,Lac -NGC7251,G,22:20:27.14,-15:46:24.8,Aqr -NGC7252,G,22:20:44.75,-24:40:41.8,Aqr -NGC7253,GPair,22:19:28.90,+29:23:30.0,Peg -NGC7253A,G,22:19:27.71,+29:23:44.9,Peg -NGC7253B,G,22:19:30.10,+29:23:16.0,Peg -NGC7254,G,22:22:36.23,-21:44:14.0,Aqr -NGC7255,G,22:23:08.00,-15:32:29.1,Aqr -NGC7256,Dup,22:22:36.23,-21:44:14.0,Aqr -NGC7257,G,22:22:36.44,-04:07:14.7,Aqr -NGC7258,G,22:22:58.07,-28:20:42.5,PsA -NGC7259,G,22:23:05.52,-28:57:17.4,PsA -NGC7260,Dup,22:22:36.44,-04:07:14.7,Aqr -NGC7261,OCl,22:20:06.39,+58:03:06.6,Cep -NGC7262,G,22:23:28.54,-32:21:51.3,PsA -NGC7263,G,22:21:45.25,+36:21:00.0,Lac -NGC7264,G,22:22:13.78,+36:23:13.2,Lac -NGC7265,G,22:22:27.44,+36:12:34.6,Lac -NGC7266,G,22:23:58.96,-04:04:24.3,Aqr -NGC7267,G,22:24:21.81,-33:41:38.7,PsA -NGC7268,GPair,22:25:41.40,-31:12:02.0,PsA -NGC7268 NED01,G,22:25:40.72,-31:12:02.7,PsA -NGC7268 NED02,G,22:25:42.16,-31:12:00.0,PsA -NGC7269,G,22:25:46.63,-13:09:59.0,Aqr -NGC7270,G,22:23:47.53,+32:24:10.9,Peg -NGC7271,G,22:23:57.59,+32:22:00.7,Peg -NGC7272,G,22:24:31.71,+16:35:17.4,Peg -NGC7273,G,22:24:09.23,+36:11:59.4,Lac -NGC7274,G,22:24:11.11,+36:07:33.1,Lac -NGC7275,G,22:24:17.23,+32:26:46.5,Peg -NGC7276,G,22:24:14.37,+36:05:15.2,Lac -NGC7277,G,22:26:10.94,-31:08:42.9,PsA -NGC7278,G,22:28:22.41,-60:10:11.4,Tuc -NGC7279,G,22:27:12.66,-35:08:25.6,PsA -NGC7280,G,22:26:27.58,+16:08:53.6,Peg -NGC7281,OCl,22:25:20.98,+57:49:16.1,Cep -NGC7282,G,22:25:53.86,+40:18:53.5,Lac -NGC7283,G,22:28:32.74,+17:28:13.2,Peg -NGC7284,G,22:28:35.93,-24:50:38.9,Aqr -NGC7285,G,22:28:38.00,-24:50:26.8,Aqr -NGC7286,G,22:27:50.52,+29:05:45.5,Peg -NGC7287,GPair,22:28:48.70,-22:12:09.0,Aqr -NGC7287A,G,22:28:48.49,-22:12:12.1,Aqr -NGC7287B,G,22:28:48.69,-22:12:06.4,Aqr -NGC7288,G,22:28:14.97,-02:53:04.4,Aqr -NGC7289,G,22:29:20.25,-35:28:19.1,PsA -NGC7290,G,22:28:26.43,+17:08:50.8,Peg -NGC7291,G,22:28:29.50,+16:46:59.3,Peg -NGC7292,G,22:28:25.91,+30:17:32.3,Peg -NGC7293,PN,22:29:38.57,-20:50:14.4,Aqr -NGC7294,G,22:32:08.06,-25:23:52.0,PsA -NGC7295,OCl,22:28:02.84,+52:17:20.7,Lac -NGC7296,Dup,22:28:02.84,+52:17:20.7,Lac -NGC7297,G,22:31:10.28,-37:49:35.2,Gru -NGC7298,G,22:30:50.65,-14:11:17.7,Aqr -NGC7299,G,22:31:33.11,-37:48:34.4,Gru -NGC7300,G,22:30:59.91,-14:00:12.7,Aqr -NGC7301,G,22:30:34.72,-17:34:25.6,Aqr -NGC7302,G,22:32:23.80,-14:07:13.9,Aqr -NGC7303,G,22:31:32.83,+30:57:21.5,Peg -NGC7304,Other,22:31:44.50,+30:58:46.8,Peg -NGC7305,G,22:32:13.94,+11:42:43.7,Peg -NGC7306,G,22:33:16.47,-27:14:47.8,PsA -NGC7307,G,22:33:52.54,-40:55:57.8,Gru -NGC7308,G,22:34:32.14,-12:56:01.9,Aqr -NGC7309,G,22:34:20.59,-10:21:25.2,Aqr -NGC7310,G,22:34:36.91,-22:29:06.3,Aqr -NGC7311,G,22:34:06.71,+05:34:11.6,Peg -NGC7312,G,22:34:34.79,+05:49:02.5,Peg -NGC7313,G,22:35:32.56,-26:06:06.4,PsA -NGC7314,G,22:35:46.19,-26:03:01.7,PsA -NGC7315,G,22:35:31.71,+34:48:12.2,Peg -NGC7316,G,22:35:56.34,+20:19:20.1,Peg -NGC7317,G,22:35:51.88,+33:56:41.6,Peg -NGC7318,GPair,22:35:57.60,+33:57:56.0,Peg -NGC7318A,G,22:35:56.75,+33:57:55.7,Peg -NGC7318B,G,22:35:58.41,+33:57:57.3,Peg -NGC7319,G,22:36:03.55,+33:58:32.6,Peg -NGC7320,G,22:36:03.38,+33:56:53.2,Peg -NGC7320C,G,22:36:20.37,+33:59:05.9,Peg -NGC7321,G,22:36:28.02,+21:37:18.5,Peg -NGC7322,G,22:37:51.46,-37:13:52.3,Gru -NGC7323,G,22:36:53.69,+19:08:37.9,Peg -NGC7324,G,22:37:00.92,+19:08:46.5,Peg -NGC7325,**,22:36:48.52,+34:22:04.9,Peg -NGC7326,**,22:36:52.09,+34:25:23.2,Peg -NGC7327,G,22:36:34.04,+34:30:13.0,Peg -NGC7328,G,22:37:29.28,+10:31:53.7,Peg -NGC7329,G,22:40:24.22,-66:28:44.3,Tuc -NGC7330,G,22:36:56.18,+38:32:53.0,Lac -NGC7331,G,22:37:04.01,+34:24:55.9,Peg -NGC7332,G,22:37:24.54,+23:47:54.0,Peg -NGC7333,*,22:37:11.64,+34:26:16.2,Peg -NGC7334,Dup,22:37:51.46,-37:13:52.3,Gru -NGC7335,G,22:37:19.39,+34:26:51.9,Peg -NGC7336,G,22:37:21.94,+34:28:54.3,Peg -NGC7337,G,22:37:26.62,+34:22:27.5,Peg -NGC7338,**,22:37:31.32,+34:24:52.1,Peg -NGC7339,G,22:37:47.24,+23:47:12.1,Peg -NGC7340,G,22:37:44.22,+34:24:36.0,Peg -NGC7341,G,22:39:05.54,-22:40:00.0,Aqr -NGC7342,G,22:38:13.15,+35:29:55.9,Peg -NGC7343,G,22:38:37.86,+34:04:17.2,Peg -NGC7344,G,22:39:36.25,-04:09:32.3,Aqr -NGC7345,G,22:38:44.86,+35:32:26.1,Peg -NGC7346,G,22:39:35.45,+11:05:00.0,Peg -NGC7347,G,22:39:56.17,+11:01:39.2,Peg -NGC7348,G,22:40:36.28,+11:54:22.4,Peg -NGC7349,G,22:41:14.90,-21:47:54.6,Aqr -NGC7350,**,22:40:48.56,+12:00:23.5,Peg -NGC7351,G,22:41:26.94,-04:26:41.0,Aqr -NGC7352,Other,22:39:43.70,+57:23:39.7,Cep -NGC7353,G,22:42:12.51,+11:52:38.3,Peg -NGC7354,PN,22:40:19.61,+61:17:05.4,Cep -NGC7355,G,22:43:30.56,-36:51:54.6,Gru -NGC7356,G,22:42:02.35,+30:42:31.7,Peg -NGC7357,G,22:42:23.94,+30:10:16.9,Peg -NGC7358,G,22:45:36.44,-65:07:18.6,Tuc -NGC7359,G,22:44:48.00,-23:41:17.1,Aqr -NGC7360,G,22:43:33.95,+04:09:04.2,Peg -NGC7361,G,22:42:17.91,-30:03:27.6,PsA -NGC7362,G,22:43:49.28,+08:42:19.6,Peg -NGC7363,G,22:43:19.91,+34:00:05.4,Peg -NGC7364,G,22:44:24.37,-00:09:43.5,Aqr -NGC7365,G,22:45:10.04,-19:57:07.3,Aqr -NGC7366,G,22:44:26.63,+10:46:52.9,Peg -NGC7367,G,22:44:34.45,+03:38:47.0,Peg -NGC7368,G,22:45:31.68,-39:20:30.6,Gru -NGC7369,G,22:44:12.30,+34:21:04.4,Peg -NGC7370,G,22:45:37.23,+11:03:28.1,Peg -NGC7371,G,22:46:03.74,-11:00:04.1,Aqr -NGC7372,G,22:45:46.00,+11:07:51.0,Peg -NGC7373,G,22:46:19.41,+03:12:36.2,Peg -NGC7374,G,22:46:00.95,+10:51:13.0,Peg -NGC7374B,G,22:45:59.19,+10:52:03.0,Peg -NGC7375,G,22:46:32.04,+21:05:01.1,Peg -NGC7376,G,22:47:17.38,+03:38:44.1,Peg -NGC7377,G,22:47:47.50,-22:18:43.6,Aqr -NGC7378,G,22:47:47.70,-11:48:59.9,Aqr -NGC7379,G,22:47:32.95,+40:14:19.7,Lac -NGC7380,Cl+N,22:47:21.01,+58:07:56.7,Cep -NGC7381,G,22:50:08.15,-19:43:30.4,Aqr -NGC7382,G,22:50:23.92,-36:51:26.3,Gru -NGC7383,G,22:49:35.62,+11:33:23.0,Peg -NGC7384,*,22:49:42.55,+11:29:14.8,Peg -NGC7385,G,22:49:54.59,+11:36:30.8,Peg -NGC7386,G,22:50:02.15,+11:41:54.1,Peg -NGC7387,G,22:50:17.64,+11:38:12.2,Peg -NGC7388,*,22:50:21.02,+11:42:39.4,Peg -NGC7389,G,22:50:16.05,+11:33:58.3,Peg -NGC7390,G,22:50:19.62,+11:31:51.7,Peg -NGC7391,G,22:50:36.13,-01:32:41.4,Aqr -NGC7392,G,22:51:48.74,-20:36:29.1,Aqr -NGC7393,G,22:51:38.09,-05:33:26.3,Aqr -NGC7394,OCl,22:50:07.68,+52:10:48.4,Lac -NGC7395,G,22:51:02.93,+37:05:16.1,Lac -NGC7396,G,22:52:22.64,+01:05:32.6,Psc -NGC7397,G,22:52:46.70,+01:07:58.0,Psc -NGC7398,G,22:52:49.27,+01:12:04.0,Psc -NGC7399,G,22:52:39.26,-09:16:04.0,Aqr -NGC7400,G,22:54:20.82,-45:20:49.3,Gru -NGC7401,G,22:52:58.56,+01:08:33.6,Psc -NGC7402,G,22:53:04.48,+01:08:40.0,Psc -NGC7403,*,22:53:06.40,+01:28:57.8,Psc -NGC7404,G,22:54:18.61,-39:18:53.8,Gru -NGC7405,G,22:52:57.17,+12:35:37.0,Peg -NGC7406,G,22:53:56.24,-06:34:45.3,Aqr -NGC7407,G,22:53:20.88,+32:07:50.4,Peg -NGC7408,G,22:55:56.86,-63:41:41.0,Tuc -NGC7409,G,22:53:48.12,+20:12:37.4,Peg -NGC7410,G,22:55:00.95,-39:39:40.8,Gru -NGC7411,G,22:54:34.92,+20:14:10.2,Peg -NGC7412,G,22:55:45.75,-42:38:31.3,Gru -NGC7412A,G,22:57:09.00,-42:48:16.1,Gru -NGC7413,G,22:55:03.10,+13:13:13.7,Peg -NGC7414,G,22:55:24.42,+13:14:53.8,Peg -NGC7415,G,22:54:53.62,+20:15:41.6,Peg -NGC7416,G,22:55:41.70,-05:29:43.2,Aqr -NGC7417,G,22:57:49.51,-65:02:18.8,Tuc -NGC7418,G,22:56:36.16,-37:01:48.3,Gru -NGC7418A,G,22:56:41.25,-36:46:21.8,Gru -NGC7419,OCl,22:54:20.06,+60:48:55.9,Cep -NGC7420,G,22:55:32.05,+29:48:18.0,Peg -NGC7421,G,22:56:54.33,-37:20:50.1,Gru -NGC7422,G,22:56:12.38,+03:55:36.4,Psc -NGC7423,OCl,22:55:08.54,+57:05:48.9,Cep -NGC7424,G,22:57:18.37,-41:04:14.1,Gru -NGC7425,G,22:57:15.54,-10:57:00.0,Aqr -NGC7426,G,22:56:02.86,+36:21:40.9,Lac -NGC7427,G,22:57:09.92,+08:30:20.1,Peg -NGC7428,G,22:57:19.53,-01:02:56.4,Psc -NGC7429,OCl,22:56:00.60,+59:58:25.9,Cep -NGC7430,G,22:57:29.72,+08:47:39.5,Peg -NGC7431,G,22:57:38.69,+26:09:51.5,Peg -NGC7432,G,22:58:01.94,+13:08:04.2,Peg -NGC7433,G,22:57:51.71,+26:09:43.8,Peg -NGC7434,G,22:58:21.45,-01:11:02.2,Psc -NGC7435,G,22:57:54.47,+26:08:19.9,Peg -NGC7436,GPair,22:57:56.90,+26:09:00.0,Peg -NGC7436A,G,22:57:56.17,+26:08:59.9,Peg -NGC7436B,G,22:57:57.54,+26:08:59.8,Peg -NGC7437,G,22:58:10.06,+14:18:30.6,Peg -NGC7438,OCl,22:57:20.37,+54:18:33.5,Cas -NGC7439,G,22:58:09.96,+29:13:42.3,Peg -NGC7440,G,22:58:32.54,+35:48:08.7,And -NGC7441,G,22:56:41.38,-07:22:44.6,Aqr -NGC7442,G,22:59:26.55,+15:32:54.2,Peg -NGC7443,G,23:00:08.85,-12:48:28.4,Aqr -NGC7444,G,23:00:08.96,-12:50:03.4,Aqr -NGC7445,G,22:59:22.44,+39:06:27.1,And -NGC7446,G,22:59:29.00,+39:04:58.4,And -NGC7447,Other,23:00:26.08,-10:31:40.8,Aqr -NGC7448,G,23:00:03.59,+15:58:49.2,Peg -NGC7449,G,22:59:37.64,+39:08:45.1,And -NGC7450,G,23:00:47.82,-12:55:06.7,Aqr -NGC7451,G,23:00:40.86,+08:28:04.5,Peg -NGC7452,G,23:00:47.50,+06:44:43.9,Psc -NGC7453,Other,23:01:25.41,-06:21:23.4,Aqr -NGC7454,G,23:01:06.51,+16:23:18.1,Peg -NGC7455,G,23:00:40.97,+07:18:11.0,Psc -NGC7456,G,23:02:10.42,-39:34:09.8,Gru -NGC7457,G,23:00:59.93,+30:08:41.8,Peg -NGC7458,G,23:01:28.64,+01:45:12.2,Psc -NGC7459,GPair,23:00:59.90,+06:45:00.0,Psc -NGC7459 NED01,G,23:00:59.64,+06:44:57.9,Psc -NGC7459 NED02,G,23:01:00.21,+06:45:03.9,Psc -NGC7460,G,23:01:42.91,+02:15:49.0,Psc -NGC7461,G,23:01:48.33,+15:34:56.9,Peg -NGC7462,G,23:02:46.49,-40:50:06.9,Gru -NGC7463,G,23:01:51.98,+15:58:54.7,Peg -NGC7464,G,23:01:53.70,+15:58:25.5,Peg -NGC7465,G,23:02:00.97,+15:57:53.2,Peg -NGC7466,G,23:02:03.43,+27:03:09.5,Peg -NGC7467,G,23:02:27.48,+15:33:14.7,Peg -NGC7468,G,23:02:59.26,+16:36:18.9,Peg -NGC7469,G,23:03:15.62,+08:52:26.4,Peg -NGC7470,G,23:05:14.06,-50:06:41.7,Gru -NGC7471,Other,23:03:53.82,-22:54:24.9,Aqr -NGC7472,G,23:05:38.61,+03:03:33.3,Psc -NGC7473,G,23:03:57.09,+30:09:36.5,Peg -NGC7474,G,23:04:04.43,+20:04:01.9,Peg -NGC7475,GPair,23:04:10.90,+20:04:52.1,Peg -NGC7475 NED01,G,23:04:10.25,+20:04:46.2,Peg -NGC7475 NED02,G,23:04:11.63,+20:05:06.1,Peg -NGC7476,G,23:05:11.87,-43:05:57.7,Gru -NGC7477,G,23:04:40.66,+03:07:04.8,Psc -NGC7478,G,23:04:56.61,+02:34:40.0,Psc -NGC7479,G,23:04:56.65,+12:19:22.4,Peg -NGC7480,G,23:05:13.62,+02:32:57.9,Psc -NGC7481,Other,23:05:51.62,-19:56:22.8,Aqr -NGC7482,Dup,23:05:38.61,+03:03:33.3,Psc -NGC7483,G,23:05:48.29,+03:32:42.4,Psc -NGC7484,G,23:07:04.91,-36:16:31.3,Scl -NGC7485,G,23:06:04.86,+34:06:27.7,Peg -NGC7486,Other,23:06:12.98,+34:06:05.4,Peg -NGC7487,G,23:06:50.54,+28:10:44.5,Peg -NGC7488,G,23:07:48.95,+00:56:25.9,Psc -NGC7489,G,23:07:32.71,+22:59:52.8,Peg -NGC7490,G,23:07:25.17,+32:22:30.2,Peg -NGC7491,G,23:08:05.98,-05:58:00.0,Aqr -NGC7492,GCl,23:08:26.68,-15:36:41.3,Aqr -NGC7493,*,23:08:31.63,+00:54:36.0,Psc -NGC7494,G,23:08:58.58,-24:22:10.5,Aqr -NGC7495,G,23:08:57.18,+12:02:52.9,Peg -NGC7496,G,23:09:47.29,-43:25:40.6,Gru -NGC7496A,G,23:12:23.28,-43:46:41.5,Gru -NGC7497,G,23:09:03.41,+18:10:37.9,Peg -NGC7498,G,23:09:56.20,-24:25:30.2,Aqr -NGC7499,G,23:10:22.39,+07:34:50.4,Psc -NGC7500,G,23:10:29.81,+11:00:44.3,Peg -NGC7501,G,23:10:30.42,+07:35:20.5,Psc -NGC7502,Other,23:10:19.79,-21:44:15.3,Aqr -NGC7503,G,23:10:42.28,+07:34:03.7,Psc -NGC7504,*,23:10:41.17,+14:23:10.7,Peg -NGC7505,G,23:11:00.65,+13:37:53.6,Peg -NGC7506,G,23:11:40.98,-02:09:36.1,Psc -NGC7507,G,23:12:07.59,-28:32:22.6,Scl -NGC7508,G,23:11:49.19,+12:56:25.5,Peg -NGC7509,G,23:12:21.41,+14:36:33.7,Peg -NGC7510,OCl,23:11:03.78,+60:34:15.2,Cep -NGC7511,G,23:12:26.29,+13:43:35.7,Peg -NGC7512,G,23:12:20.93,+31:07:32.2,Peg -NGC7513,G,23:13:14.03,-28:21:27.0,Scl -NGC7514,G,23:12:25.86,+34:52:53.4,Peg -NGC7515,G,23:12:48.67,+12:40:45.4,Peg -NGC7516,G,23:12:51.86,+20:14:54.3,Peg -NGC7517,G,23:13:13.83,-02:06:01.5,Psc -NGC7518,G,23:13:12.73,+06:19:18.0,Psc -NGC7519,G,23:13:11.24,+10:46:19.8,Peg -NGC7520,Other,23:13:44.88,-23:47:39.1,Aqr -NGC7521,G,23:13:35.35,-01:43:53.4,Psc -NGC7522,*,23:15:36.40,-22:53:41.5,Aqr -NGC7523,G,23:13:34.72,+13:59:12.5,Peg -NGC7524,G,23:13:46.55,-01:43:48.3,Psc -NGC7525,GPair,23:13:40.36,+14:01:22.7,Peg -NGC7525 NED01,G,23:13:40.27,+14:01:27.8,Peg -NGC7525 NED02,G,23:13:40.50,+14:01:15.6,Peg -NGC7526,Other,23:14:02.33,-09:13:17.9,Aqr -NGC7527,G,23:13:41.77,+24:54:08.2,Peg -NGC7528,G,23:14:20.26,+10:13:53.2,Peg -NGC7529,G,23:14:03.19,+08:59:33.0,Peg -NGC7530,G,23:14:11.83,-02:46:45.6,Psc -NGC7531,G,23:14:48.50,-43:35:59.8,Gru -NGC7532,G,23:14:22.23,-02:43:41.5,Psc -NGC7533,G,23:14:22.08,-02:02:01.4,Psc -NGC7534,G,23:14:26.68,-02:41:53.7,Psc -NGC7535,G,23:14:12.77,+13:34:54.8,Peg -NGC7536,G,23:14:13.18,+13:25:35.0,Peg -NGC7537,G,23:14:34.50,+04:29:54.1,Psc -NGC7538,Cl+N,23:13:38.63,+61:30:44.6,Cep -NGC7539,G,23:14:29.44,+23:41:05.4,Peg -NGC7540,G,23:14:36.07,+15:57:01.1,Peg -NGC7541,G,23:14:43.89,+04:32:03.7,Psc -NGC7542,G,23:14:41.63,+10:38:35.7,Peg -NGC7543,G,23:14:34.58,+28:19:38.0,Peg -NGC7544,G,23:14:56.98,-02:11:57.6,Psc -NGC7545,G,23:15:32.10,-38:32:08.1,Gru -NGC7546,G,23:15:05.64,-02:19:29.1,Psc -NGC7547,G,23:15:03.40,+18:58:24.4,Peg -NGC7548,G,23:15:11.11,+25:16:55.0,Peg -NGC7549,G,23:15:17.23,+19:02:30.1,Peg -NGC7550,G,23:15:16.01,+18:57:42.5,Peg -NGC7551,G,23:15:22.03,+15:56:27.6,Peg -NGC7552,G,23:16:10.76,-42:35:05.1,Gru -NGC7553,G,23:15:33.09,+19:02:53.4,Peg -NGC7554,G,23:15:41.34,-02:22:43.0,Psc -NGC7555,Other,23:15:30.85,+12:34:22.3,Peg -NGC7556,G,23:15:44.47,-02:22:53.4,Psc -NGC7556A,G,23:15:43.73,-02:23:08.0,Psc -NGC7557,G,23:15:39.77,+06:42:30.0,Psc -NGC7558,G,23:15:38.23,+18:55:10.9,Peg -NGC7559,GPair,23:15:46.30,+13:17:38.0,Peg -NGC7559A,G,23:15:45.99,+13:17:49.0,Peg -NGC7559B,G,23:15:46.56,+13:17:25.0,Peg -NGC7560,**,23:15:53.76,+04:29:45.1,Psc -NGC7561,*,23:15:57.42,+04:31:21.8,Psc -NGC7562,G,23:15:57.50,+06:41:15.1,Psc -NGC7562A,G,23:16:01.46,+06:39:08.2,Psc -NGC7563,G,23:15:55.93,+13:11:46.0,Peg -NGC7564,*,23:16:01.19,+07:20:53.0,Psc -NGC7565,Other,23:16:19.81,-00:03:31.0,Psc -NGC7566,G,23:16:37.43,-02:19:50.0,Psc -NGC7567,G,23:16:10.28,+15:51:00.1,Peg -NGC7568,G,23:16:24.87,+24:29:49.3,Peg -NGC7569,G,23:16:44.55,+08:54:19.6,Peg -NGC7570,G,23:16:44.67,+13:28:58.8,Peg -NGC7571,G,23:18:30.26,+18:41:19.5,Peg -NGC7572,G,23:16:50.37,+18:28:59.4,Peg -NGC7573,G,23:16:26.35,-22:09:15.8,Aqr -NGC7574,Dup,23:16:24.87,+24:29:49.3,Peg -NGC7575,G,23:17:20.87,+05:39:38.8,Psc -NGC7576,G,23:17:22.74,-04:43:40.3,Aqr -NGC7577,G,23:17:17.10,+07:21:55.5,Psc -NGC7578,GPair,23:17:12.70,+18:42:16.0,Peg -NGC7578A,G,23:17:11.94,+18:42:04.7,Peg -NGC7578B,G,23:17:13.56,+18:42:29.7,Peg -NGC7579,G,23:17:38.85,+09:25:59.9,Peg -NGC7580,G,23:17:36.40,+14:00:04.3,Peg -NGC7581,Dup,23:14:43.89,+04:32:03.7,Psc -NGC7582,G,23:18:23.50,-42:22:14.0,Gru -NGC7583,G,23:17:52.77,+07:22:46.0,Psc -NGC7584,G,23:17:53.00,+09:26:00.0,Peg -NGC7585,G,23:18:01.34,-04:39:01.1,Aqr -NGC7586,G,23:17:55.60,+08:35:02.7,Peg -NGC7587,G,23:17:59.13,+09:40:48.7,Peg -NGC7588,G,23:17:57.81,+18:45:07.8,Peg -NGC7589,G,23:18:15.67,+00:15:40.2,Psc -NGC7590,G,23:18:54.81,-42:14:20.6,Gru -NGC7591,G,23:18:16.28,+06:35:08.9,Psc -NGC7592,GTrpl,23:18:22.20,-04:25:01.0,Aqr -NGC7592A,G,23:18:21.80,-04:24:56.7,Aqr -NGC7592B,G,23:18:22.59,-04:24:58.1,Aqr -NGC7592C,G,23:18:22.13,-04:25:08.0,Aqr -NGC7593,G,23:17:56.99,+11:20:57.0,Peg -NGC7594,G,23:18:13.92,+10:17:53.9,Peg -NGC7595,G,23:18:30.23,+09:55:56.7,Peg -NGC7596,G,23:17:12.00,-06:54:43.2,Aqr -NGC7597,Dup,23:18:30.26,+18:41:19.5,Peg -NGC7598,G,23:18:33.27,+18:44:57.7,Peg -NGC7599,G,23:19:21.14,-42:15:24.6,Gru -NGC7600,G,23:18:53.86,-07:34:49.6,Aqr -NGC7601,G,23:18:47.05,+09:14:01.0,Peg -NGC7602,G,23:18:43.53,+18:41:54.1,Peg -NGC7603,G,23:18:56.62,+00:14:38.2,Psc -NGC7604,G,23:17:51.90,+07:25:47.6,Psc -NGC7605,Dup,23:17:52.77,+07:22:46.0,Psc -NGC7606,G,23:19:04.78,-08:29:06.3,Aqr -NGC7607,**,23:18:59.24,+11:20:30.2,Peg -NGC7608,G,23:19:15.33,+08:21:00.5,Peg -NGC7609,GPair,23:19:30.50,+09:30:19.0,Peg -NGC7609 NED01,G,23:19:30.05,+09:30:29.6,Peg -NGC7609 NED02,G,23:19:31.09,+09:30:10.7,Peg -NGC7610,G,23:19:41.37,+10:11:06.0,Peg -NGC7611,G,23:19:36.60,+08:03:47.7,Psc -NGC7612,G,23:19:44.20,+08:34:35.0,Peg -NGC7613,Other,23:19:51.76,+00:11:55.9,Psc -NGC7614,Other,23:19:51.76,+00:11:55.9,Psc -NGC7615,G,23:19:54.44,+08:23:57.9,Peg -NGC7616,Dup,23:19:41.37,+10:11:06.0,Peg -NGC7617,G,23:20:08.97,+08:09:56.8,Psc -NGC7618,G,23:19:47.22,+42:51:09.5,And -NGC7619,G,23:20:14.53,+08:12:22.5,Peg -NGC7620,G,23:20:05.68,+24:13:16.1,Peg -NGC7621,G,23:20:24.62,+08:21:58.7,Peg -NGC7622,G,23:21:38.53,-62:07:03.9,Tuc -NGC7623,G,23:20:30.02,+08:23:44.9,Peg -NGC7624,G,23:20:22.61,+27:18:56.5,Peg -NGC7625,G,23:20:30.13,+17:13:32.0,Peg -NGC7626,G,23:20:42.55,+08:13:01.1,Peg -NGC7627,G,23:22:30.93,+11:53:33.2,Peg -NGC7628,G,23:20:54.88,+25:53:54.8,Peg -NGC7629,G,23:21:19.37,+01:24:11.3,Psc -NGC7630,G,23:21:16.31,+11:23:50.4,Peg -NGC7631,G,23:21:26.67,+08:13:03.5,Peg -NGC7632,G,23:22:00.90,-42:28:49.8,Gru -NGC7633,G,23:23:03.30,-67:39:13.4,Ind -NGC7634,G,23:21:41.76,+08:53:12.9,Peg -NGC7635,HII,23:20:45.60,+61:12:44.5,Cas -NGC7636,G,23:22:32.96,-29:16:50.5,Scl -NGC7637,G,23:26:27.66,-81:54:41.7,Oct -NGC7638,GPair,23:22:33.12,+11:19:43.7,Peg -NGC7639,G,23:22:48.23,+11:22:22.3,Peg -NGC7640,G,23:22:06.58,+40:50:43.5,And -NGC7641,Dup,23:22:30.93,+11:53:33.2,Peg -NGC7642,G,23:22:53.40,+01:26:34.0,Psc -NGC7643,G,23:22:50.40,+11:59:19.8,Peg -NGC7644,Dup,23:22:50.40,+11:59:19.8,Peg -NGC7645,G,23:23:47.29,-29:23:16.9,Scl -NGC7646,G,23:24:06.96,-11:51:38.5,Aqr -NGC7647,G,23:23:57.43,+16:46:38.2,Peg -NGC7648,G,23:23:54.02,+09:40:02.9,Peg -NGC7649,G,23:24:20.09,+14:38:49.7,Peg -NGC7650,G,23:25:21.45,-57:47:28.9,Tuc -NGC7651,GPair,23:24:25.80,+13:58:12.0,Peg -NGC7651 NED01,G,23:24:25.97,+13:58:20.2,Peg -NGC7651 NED02,G,23:24:25.67,+13:58:01.6,Peg -NGC7652,G,23:25:37.43,-57:53:14.6,Tuc -NGC7653,G,23:24:49.36,+15:16:32.1,Peg -NGC7654,OCl,23:24:48.40,+61:35:35.4,Cas -NGC7655,G,23:26:45.94,-68:01:39.1,Ind -NGC7656,G,23:24:31.41,-19:03:32.6,Aqr -NGC7657,G,23:26:47.51,-57:48:20.7,Tuc -NGC7658,GPair,23:26:24.70,-39:13:18.5,Gru -NGC7658A,G,23:26:24.67,-39:12:57.5,Gru -NGC7658B,G,23:26:24.85,-39:13:35.8,Gru -NGC7659,G,23:25:55.68,+14:12:35.4,Peg -NGC7660,G,23:25:48.66,+27:01:47.6,Peg -NGC7661,G,23:27:14.29,-65:16:13.5,Tuc -NGC7662,PN,23:25:53.90,+42:32:05.8,And -NGC7663,G,23:26:45.22,-04:58:00.0,Aqr -NGC7664,G,23:26:39.76,+25:04:48.5,Peg -NGC7665,G,23:27:14.80,-09:23:13.2,Aqr -NGC7666,Other,23:27:24.51,-04:11:10.7,Aqr -NGC7667,G,23:24:23.10,-00:06:29.0,Psc -NGC7668,Other,23:27:21.82,-00:11:28.7,Psc -NGC7669,Other,23:27:21.82,-00:11:28.7,Psc -NGC7670,Other,23:27:21.82,-00:11:28.7,Psc -NGC7671,G,23:27:19.34,+12:28:02.7,Peg -NGC7672,G,23:27:31.44,+12:23:06.7,Peg -NGC7673,G,23:27:41.02,+23:35:20.5,Peg -NGC7674,G,23:27:56.72,+08:46:44.5,Peg -NGC7675,G,23:28:05.92,+08:46:06.9,Peg -NGC7676,G,23:29:01.72,-59:43:00.0,Tuc -NGC7677,G,23:28:06.12,+23:31:53.0,Peg -NGC7678,G,23:28:27.90,+22:25:16.3,Peg -NGC7679,G,23:28:46.70,+03:30:41.3,Psc -NGC7680,G,23:28:35.10,+32:24:56.6,Peg -NGC7681,G,23:28:54.89,+17:18:34.7,Peg -NGC7682,G,23:29:03.93,+03:32:00.0,Psc -NGC7683,G,23:29:03.82,+11:26:42.6,Peg -NGC7684,G,23:30:32.04,+00:04:51.8,Psc -NGC7685,G,23:30:33.50,+03:54:05.7,Psc -NGC7686,OCl,23:30:07.38,+49:08:02.8,And -NGC7687,G,23:30:54.45,+03:32:47.8,Psc -NGC7688,G,23:31:05.50,+21:24:41.5,Peg -NGC7689,G,23:33:16.73,-54:05:40.1,Phe -NGC7690,G,23:33:02.56,-51:41:54.1,Phe -NGC7691,G,23:32:24.42,+15:50:52.2,Peg -NGC7692,G,23:32:46.76,-05:35:48.8,Aqr -NGC7693,G,23:33:10.51,-01:17:30.7,Psc -NGC7694,G,23:33:15.77,-02:42:10.4,Psc -NGC7695,G,23:33:15.00,-02:43:12.7,Psc -NGC7696,G,23:33:50.11,+04:52:14.9,Psc -NGC7697,G,23:34:52.98,-65:23:45.7,Tuc -NGC7698,G,23:34:01.58,+24:56:40.6,Peg -NGC7699,G,23:34:27.03,-02:53:58.0,Psc -NGC7700,G,23:34:30.29,-02:57:13.2,Psc -NGC7701,G,23:34:31.50,-02:51:15.4,Psc -NGC7702,G,23:35:28.88,-56:00:44.2,Phe -NGC7703,G,23:34:46.87,+16:04:32.8,Peg -NGC7704,G,23:35:01.03,+04:53:51.1,Psc -NGC7705,G,23:35:02.52,+04:48:13.7,Psc -NGC7706,G,23:35:10.45,+04:57:51.1,Psc -NGC7707,G,23:34:51.40,+44:18:14.6,And -NGC7708,OCl,23:35:01.46,+72:49:59.4,Cep -NGC7709,G,23:35:27.47,-16:42:18.3,Aqr -NGC7710,G,23:35:46.14,-02:52:51.3,Psc -NGC7711,G,23:35:39.37,+15:18:07.1,Peg -NGC7712,G,23:35:51.64,+23:37:07.5,Peg -NGC7713,G,23:36:14.99,-37:56:17.1,Scl -NGC7713A,G,23:37:08.29,-37:42:50.9,Scl -NGC7714,G,23:36:14.10,+02:09:18.6,Psc -NGC7715,G,23:36:22.14,+02:09:23.5,Psc -NGC7716,G,23:36:31.45,+00:17:50.2,Psc -NGC7717,G,23:37:43.67,-15:07:06.6,Aqr -NGC7718,G,23:38:05.01,+25:43:11.0,Peg -NGC7719,G,23:38:02.57,-22:58:27.9,Aqr -NGC7720,GPair,23:38:29.39,+27:01:53.2,Peg -NGC7720 NED01,G,23:38:29.40,+27:01:53.3,Peg -NGC7720A,G,23:38:29.54,+27:02:05.1,Peg -NGC7721,G,23:38:48.65,-06:31:04.3,Aqr -NGC7722,G,23:38:41.20,+15:57:16.7,Peg -NGC7723,G,23:38:57.08,-12:57:39.9,Aqr -NGC7724,G,23:39:07.16,-12:13:26.6,Aqr -NGC7725,G,23:39:14.78,-04:32:21.8,Aqr -NGC7726,G,23:39:11.91,+27:06:55.2,Peg -NGC7727,G,23:39:53.72,-12:17:34.0,Aqr -NGC7728,G,23:40:00.84,+27:08:01.4,Peg -NGC7729,G,23:40:33.65,+29:11:17.4,Peg -NGC7730,G,23:40:45.95,-20:30:31.7,Aqr -NGC7731,G,23:41:29.07,+03:44:24.1,Psc -NGC7732,G,23:41:33.87,+03:43:29.8,Psc -NGC7733,G,23:42:32.95,-65:57:23.4,Tuc -NGC7734,G,23:42:42.93,-65:56:40.7,Tuc -NGC7735,G,23:42:17.31,+26:13:54.3,Peg -NGC7736,G,23:42:25.78,-19:27:08.3,Aqr -NGC7737,G,23:42:46.41,+27:03:10.6,Peg -NGC7738,G,23:44:02.06,+00:30:59.9,Psc -NGC7739,G,23:44:30.08,+00:19:13.7,Psc -NGC7740,G,23:43:32.28,+27:18:42.8,Peg -NGC7741,G,23:43:54.37,+26:04:32.2,Peg -NGC7742,G,23:44:15.73,+10:46:01.5,Peg -NGC7743,G,23:44:21.14,+09:56:02.7,Peg -NGC7744,G,23:44:59.24,-42:54:39.3,Phe -NGC7745,G,23:44:45.79,+25:54:32.3,Peg -NGC7746,G,23:45:20.00,-01:41:05.6,Psc -NGC7747,G,23:45:32.25,+27:21:39.4,Peg -NGC7748,*,23:44:56.67,+69:45:17.5,Cep -NGC7749,G,23:45:47.55,-29:31:04.1,Scl -NGC7750,G,23:46:37.84,+03:47:59.3,Psc -NGC7751,G,23:46:58.34,+06:51:42.5,Psc -NGC7752,G,23:46:58.56,+29:27:32.1,Peg -NGC7753,G,23:47:04.83,+29:29:00.4,Peg -NGC7754,G,23:49:11.21,-16:36:02.1,Aqr -NGC7755,G,23:47:51.76,-30:31:19.3,Scl -NGC7756,*,23:48:28.60,+04:07:30.6,Psc -NGC7757,G,23:48:45.52,+04:10:16.1,Psc -NGC7758,G,23:48:55.18,-22:01:27.1,Aqr -NGC7759,G,23:48:54.70,-16:32:28.2,Aqr -NGC7760,G,23:49:11.94,+30:58:59.3,Peg -NGC7761,G,23:51:28.89,-13:22:53.7,Aqr -NGC7762,OCl,23:50:01.75,+68:02:16.7,Cep -NGC7763,G,23:50:15.70,-16:35:24.0,Aqr -NGC7764,G,23:50:53.98,-40:43:41.6,Phe -NGC7764A,G,23:53:22.5,-40:48:37,Phe -NGC7765,G,23:50:52.16,+27:09:58.6,Peg -NGC7766,G,23:50:55.91,+27:07:34.7,Peg -NGC7767,G,23:50:56.35,+27:05:13.7,Peg -NGC7768,G,23:50:58.58,+27:08:50.6,Peg -NGC7769,G,23:51:03.97,+20:09:01.5,Peg -NGC7770,G,23:51:22.54,+20:05:47.5,Peg -NGC7771,G,23:51:24.81,+20:06:42.3,Peg -NGC7772,Other,23:51:45.96,+16:14:53.3,Peg -NGC7773,G,23:52:09.89,+31:16:35.8,Peg -NGC7774,GPair,23:52:11.20,+11:28:12.0,Peg -NGC7774 NED01,G,23:52:10.69,+11:28:13.1,Peg -NGC7774 NED02,G,23:52:11.69,+11:28:11.0,Peg -NGC7775,G,23:52:24.45,+28:46:21.6,Peg -NGC7776,G,23:54:16.56,-13:35:11.2,Aqr -NGC7777,G,23:53:12.50,+28:17:00.4,Peg -NGC7778,G,23:53:19.67,+07:52:15.3,Psc -NGC7779,G,23:53:26.79,+07:52:32.4,Psc -NGC7780,G,23:53:32.17,+08:07:05.3,Psc -NGC7781,G,23:53:45.98,+07:51:37.7,Psc -NGC7782,G,23:53:53.89,+07:58:14.0,Psc -NGC7783,GPair,23:54:11.06,+00:22:47.8,Psc -NGC7783 NED01,G,23:54:10.08,+00:22:58.3,Psc -NGC7783 NED02,G,23:54:12.08,+00:22:37.3,Psc -NGC7784,G,23:55:13.67,+21:45:43.8,Peg -NGC7785,G,23:55:19.03,+05:54:57.0,Psc -NGC7786,G,23:55:21.54,+21:35:17.0,Peg -NGC7787,G,23:56:07.82,+00:32:58.1,Psc -NGC7788,OCl,23:56:45.58,+61:23:59.7,Cas -NGC7789,OCl,23:57:24.06,+56:42:29.8,Cas -NGC7790,OCl,23:58:24.26,+61:12:29.9,Cas -NGC7791,**,23:57:57.30,+10:45:56.6,Peg -NGC7792,G,23:58:03.57,+16:30:05.1,Peg -NGC7793,G,23:57:49.83,-32:35:27.7,Scl -NGC7794,G,23:58:34.12,+10:43:41.3,Peg -NGC7795,OCl,23:58:37.44,+60:02:05.9,Cas -NGC7796,G,23:58:59.77,-55:27:29.9,Phe -NGC7797,G,23:58:58.87,+03:38:04.7,Psc -NGC7798,G,23:59:25.50,+20:44:59.5,Peg -NGC7799,*,23:59:31.55,+31:17:44.2,Peg -NGC7800,G,23:59:36.32,+14:48:20.1,Peg -NGC7801,OCl,00:00:21.44,+50:44:42.0,Cas -NGC7802,G,00:01:00.42,+06:14:31.4,Psc -NGC7803,G,00:01:19.97,+13:06:40.5,Peg -NGC7804,**,00:01:18.80,+07:44:58.9,Psc -NGC7805,G,00:01:26.77,+31:26:01.5,Peg -NGC7806,G,00:01:30.05,+31:26:30.7,Peg -NGC7807,G,00:00:26.58,-18:50:30.8,Cet -NGC7808,G,00:03:32.13,-10:44:40.8,Cet -NGC7809,G,00:02:09.45,+02:56:27.8,Psc -NGC7810,G,00:02:19.16,+12:58:17.9,Peg -NGC7811,G,00:02:26.47,+03:21:06.9,Psc -NGC7812,G,00:02:54.47,-34:14:08.3,Scl -NGC7813,G,00:04:09.12,-11:59:02.0,Cet -NGC7814,G,00:03:14.89,+16:08:43.5,Peg -NGC7815,*,00:03:24.83,+20:42:15.0,Peg -NGC7816,G,00:03:48.86,+07:28:43.1,Psc -NGC7817,G,00:03:58.91,+20:45:08.4,Peg -NGC7818,G,00:04:08.85,+07:22:45.8,Psc -NGC7819,G,00:04:24.54,+31:28:19.4,Peg -NGC7820,G,00:04:30.79,+05:12:01.0,Psc -NGC7821,G,00:05:16.72,-16:28:36.9,Cet -NGC7822,HII,00:03:35.35,+67:09:41.9,Cep -NGC7823,G,00:04:45.59,-62:03:41.6,Tuc -NGC7824,G,00:05:06.23,+06:55:12.4,Psc -NGC7825,G,00:05:06.60,+05:12:13.2,Psc -NGC7826,Other,00:05:19.32,-20:41:17.8,Cet -NGC7827,G,00:05:27.66,+05:13:20.4,Psc -NGC7828,G,00:06:27.07,-13:24:58.0,Cet -NGC7829,G,00:06:28.98,-13:25:14.2,Cet -NGC7830,*,00:06:01.86,+08:20:34.0,Psc -NGC7831,G,00:07:19.53,+32:36:33.3,And -NGC7832,G,00:06:28.46,-03:42:58.1,Psc -NGC7833,Other,00:06:31.46,+27:38:22.8,Peg -NGC7834,G,00:06:37.82,+08:22:04.4,Psc -NGC7835,G,00:06:46.77,+08:25:33.4,Psc -NGC7836,G,00:08:01.64,+33:04:14.8,And -NGC7837,G,00:06:51.39,+08:21:05.0,Psc -NGC7838,G,00:06:53.95,+08:21:03.1,Psc -NGC7839,**,00:07:00.63,+27:38:06.8,Peg -NGC7840,G,00:07:08.80,+08:23:00.6,Psc diff --git a/modules/oxygen_celestial/data.db b/modules/oxygen_celestial/data.db deleted file mode 100644 index 750035ba04c250e16ed7a097c4e17d0948811c4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1568768 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�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
    OwP~#a?** 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
  1. n%DD+e|7V)?2h5wi%L$ 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

      b1Dc7v$>kq|VXeGVIdCt-S30 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

        eF7nmlZ#bt7S<;a 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 diff --git a/modules/oxygen_celestial/gps3.py b/modules/oxygen_celestial/gps3.py deleted file mode 100644 index c18c1c8d..00000000 --- a/modules/oxygen_celestial/gps3.py +++ /dev/null @@ -1,196 +0,0 @@ -# coding=utf-8 - -""" - -Copyright(c) 2022-2023 Max Qian - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License version 3 as published by the Free Software Foundation. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. -You should have received a copy of the GNU Library General Public License -along with this library; see the file COPYING.LIB. If not, write to -the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -Boston, MA 02110-1301, USA. - -""" - -""" - GPS3 (gps3.py) is a Python 2.7-3.5 GPSD interface (http://www.catb.org/gpsd) - Default host='127.0.0.1', port=2947, gpsd_protocol='json' in two classes. - 1) 'GPSDSocket' creates a GPSD socket connection & request/retrieve GPSD output. - 2) 'DataStream' Streamed gpsd JSON data literates it into python dictionaries. - Import from gps3 import gps3 - Instantiate gpsd_socket = gps3.GPSDSocket() - data_stream = gps3.DataStream() - Run gpsd_socket.connect() - gpsd_socket.watch() - Iterate for new_data in gpsd_socket: - if new_data: - data_stream.unpack(new_data) - Use print('Altitude = ',data_stream.TPV['alt']) - print('Latitude = ',data_stream.TPV['lat']) - Consult Lines 144-ff for Attribute/Key possibilities. - or http://www.catb.org/gpsd/gpsd_json.html - Run human.py; python[X] human.py [arguments] for a human experience. -""" - -import json -import select -import socket -import sys - -HOST = '127.0.0.1' # gpsd -GPSD_PORT = 2947 # defaults -PROTOCOL = 'json' # " - -class GPSDSocket(object): - """Establish a socket with gpsd, by which to send commands and receive data.""" - - def __init__(self): - self.streamSock = None - self.response = None - - def connect(self, host=HOST, port=GPSD_PORT): - """Connect to a host on a given port. - Arguments: - host: default host='127.0.0.1' - port: default port=2947 - """ - for alotta_stuff in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): - family, socktype, proto, _canonname, host_port = alotta_stuff - try: - self.streamSock = socket.socket(family, socktype, proto) - self.streamSock.connect(host_port) - self.streamSock.setblocking(False) - except (OSError, IOError) as error: - sys.stderr.write('\r\nGPSDSocket.connect exception is--> {}'.format(error)) - sys.stderr.write('\r\nGPS3 gpsd connection at \'{0}\' on port \'{1}\' failed\r\n'.format(host, port)) - - def watch(self, enable=True, gpsd_protocol=PROTOCOL, devicepath=None): - """watch gpsd in various gpsd_protocols or devices. - Arguments: - enable: (bool) stream data to socket - gpsd_protocol: (str) 'json' | 'nmea' | 'rare' | 'raw' | 'scaled' | 'split24' | 'pps' - devicepath: (str) device path - '/dev/ttyUSBn' for some number n or '/dev/whatever_works' - Returns: - command: (str) e.g., '?WATCH={"enable":true,"json":true};' - """ - # N.B.: 'timing' requires special attention, as it is undocumented and lives with dragons. - command = '?WATCH={{"enable":true,"{0}":true}}'.format(gpsd_protocol) - - if gpsd_protocol == 'rare': # 1 for a channel, gpsd reports the unprocessed NMEA or AIVDM data stream - command = command.replace('"rare":true', '"raw":1') - if gpsd_protocol == 'raw': # 2 channel that processes binary data, received data verbatim without hex-dumping. - command = command.replace('"raw":true', '"raw",2') - if not enable: - command = command.replace('true', 'false') # sets -all- command values false . - if devicepath: - command = command.replace('}', ',"device":"') + devicepath + '"}' - - return self.send(command) - - def send(self, command): - """Ship commands to the daemon - Arguments: - command: e.g., '?WATCH={{'enable':true,'json':true}}'|'?VERSION;'|'?DEVICES;'|'?DEVICE;'|'?POLL;' - """ - # The POLL command requests data from the last-seen fixes on all active GPS devices. - # Devices must previously have been activated by ?WATCH to be pollable. - try: - self.streamSock.send(bytes(command, encoding='utf-8')) - except TypeError: - self.streamSock.send(command) # 2.7 chokes on 'bytes' and 'encoding=' - except (OSError, IOError) as error: # MOE, LEAVE THIS ALONE!...for now. - sys.stderr.write('\nGPS3 send command fail with {}\n'.format(error)) # [Errno 107] typically no socket - - def __iter__(self): - """banana""" # <--- for scale - return self - - def next(self, timeout=0): - """Return empty unless new data is ready for the client. - Arguments: - timeout: Default timeout=0 range zero to float specifies a time-out as a floating point - number in seconds. Will sit and wait for timeout seconds. When the timeout argument is omitted - the function blocks until at least one file descriptor is ready. A time-out value of zero specifies - a poll and never blocks. - """ - try: - waitin, _waitout, _waiterror = select.select((self.streamSock,), (), (), timeout) - if not waitin: return - else: - gpsd_response = self.streamSock.makefile() # '.makefile(buffering=4096)' In strictly Python3 - self.response = gpsd_response.readline() - return self.response - - except StopIteration as error: - sys.stderr.write('The readline exception in GPSDSocket.next is--> {}'.format(error)) - - __next__ = next # Workaround for changes in iterating between Python 2.7 and 3 - - def close(self): - """turn off stream and close socket""" - if self.streamSock: - self.watch(enable=False) - self.streamSock.close() - self.streamSock = None - - -class DataStream(object): - """Retrieve JSON Object(s) from GPSDSocket and unpack it into respective - gpsd 'class' dictionaries, TPV, SKY, etc. yielding hours of fun and entertainment. - """ - packages = { - 'VERSION': {'release', 'proto_major', 'proto_minor', 'remote', 'rev'}, - 'TPV': {'alt', 'climb', 'device', 'epc', 'epd', 'eps', 'ept', 'epv', 'epx', 'epy', 'lat', 'lon', 'mode', 'speed', 'tag', 'time', 'track'}, - 'SKY': {'satellites', 'gdop', 'hdop', 'pdop', 'tdop', 'vdop', 'xdop', 'ydop'}, - # Subset of SKY: 'satellites': {'PRN', 'ss', 'el', 'az', 'used'} # is always present. - 'GST': {'alt', 'device', 'lat', 'lon', 'major', 'minor', 'orient', 'rms', 'time'}, - 'ATT': {'acc_len', 'acc_x', 'acc_y', 'acc_z', 'depth', 'device', 'dip', 'gyro_x', 'gyro_y', 'heading', 'mag_len', 'mag_st', 'mag_x', - 'mag_y', 'mag_z', 'pitch', 'pitch_st', 'roll', 'roll_st', 'temperature', 'time', 'yaw', 'yaw_st'}, - # 'POLL': {'active', 'tpv', 'sky', 'time'}, - 'PPS': {'device', 'clock_sec', 'clock_nsec', 'real_sec', 'real_nsec', 'precision'}, - 'TOFF': {'device', 'clock_sec', 'clock_nsec', 'real_sec', 'real_nsec'}, - 'DEVICES': {'devices', 'remote'}, - 'DEVICE': {'activated', 'bps', 'cycle', 'mincycle', 'driver', 'flags', 'native', 'parity', 'path', 'stopbits', 'subtype'}, - # 'AIS': {} # see: http://catb.org/gpsd/AIVDM.html - 'ERROR': {'message'}} # TODO: Full suite of possible GPSD output - - def __init__(self): - """Potential data packages from gpsd for a generator of class attribute dictionaries""" - for package_name, dataset in self.packages.items(): - _emptydict = {key: 'n/a' for key in dataset} - setattr(self, package_name, _emptydict) - - self.DEVICES['devices'] = {key: 'n/a' for key in self.packages['DEVICE']} # How does multiple listed devices work? - # self.POLL = {'tpv': self.TPV, 'sky': self.SKY, 'time': 'n/a', 'active': 'n/a'} - - def unpack(self, gpsd_socket_response): - """Sets new socket data as DataStream attributes in those initialised dictionaries - Arguments: - gpsd_socket_response (json object): - Provides: - self attribute dictionaries, e.g., self.TPV['lat'], self.SKY['gdop'] - Raises: - AttributeError: 'str' object has no attribute 'keys' when the device falls out of the system - ValueError, KeyError: most likely extra, or mangled JSON data, should not happen, but that - applies to a lot of things. - """ - try: - fresh_data = json.loads(gpsd_socket_response) # The reserved word 'class' is popped from JSON object class - package_name = fresh_data.pop('class', 'ERROR') # gpsd data package errors are also 'ERROR'. - package = getattr(self, package_name, package_name) # packages are named for JSON object class - for key in package.keys(): - package[key] = fresh_data.get(key, 'n/a') # Restores 'n/a' if key is absent in the socket response - - except AttributeError: # 'str' object has no attribute 'keys' - sys.stderr.write('There is an unexpected exception in DataStream.unpack') - return - - except (ValueError, KeyError) as error: - sys.stderr.write(str(error)) # Extra data or aberrant data in stream. - return diff --git a/modules/oxygen_celestial/location.conf b/modules/oxygen_celestial/location.conf deleted file mode 100644 index e13984fb..00000000 --- a/modules/oxygen_celestial/location.conf +++ /dev/null @@ -1,4 +0,0 @@ -[default] -latitude = 30.13791 -longitude = 120.00704 -elevation = 10.0 \ No newline at end of file diff --git a/modules/oxygen_celestial/object.py b/modules/oxygen_celestial/object.py deleted file mode 100644 index f8f71464..00000000 --- a/modules/oxygen_celestial/object.py +++ /dev/null @@ -1,612 +0,0 @@ -# coding=utf-8 - -""" - -Copyright(c) 2022-2023 Max Qian - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License version 3 as published by the Free Software Foundation. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. -You should have received a copy of the GNU Library General Public License -along with this library; see the file COPYING.LIB. If not, write to -the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -Boston, MA 02110-1301, USA. - -""" - -import datetime -import ephem -import numpy - - -def parser(observer: ephem.Observer, body) -> list: - positions = [] - - # test for always below horizon or always above horizon - try: - if ( - ephem.localtime(observer.previous_rising(body)).date() - == ephem.localtime(observer.date).date() - and observer.previous_rising(body) - < observer.previous_transit(body) - < observer.previous_setting(body) - < observer.date - ): - positions.append(observer.previous_rising(body)) - positions.append(observer.previous_transit(body)) - positions.append(observer.previous_setting(body)) - elif ephem.localtime(observer.previous_rising(body)).date() == ephem.localtime( - observer.date - ).date() and observer.previous_rising(body) < observer.previous_transit( - body - ) < observer.date < observer.next_setting( - body - ): - positions.append(observer.previous_rising(body)) - positions.append(observer.previous_transit(body)) - positions.append(observer.next_setting(body)) - elif ephem.localtime(observer.previous_rising(body)).date() == ephem.localtime( - observer.date - ).date() and observer.previous_rising( - body - ) < observer.date < observer.next_transit( - body - ) < observer.next_setting( - body - ): - positions.append(observer.previous_rising(body)) - positions.append(observer.next_transit(body)) - positions.append(observer.next_setting(body)) - elif ephem.localtime(observer.previous_rising(body)).date() == ephem.localtime( - observer.date - ).date() and observer.date < observer.next_rising(body) < observer.next_transit( - body - ) < observer.next_setting( - body - ): - positions.append(observer.next_rising(body)) - positions.append(observer.next_transit(body)) - positions.append(observer.next_setting(body)) - else: - positions.append(observer.next_rising(body)) - positions.append(observer.next_transit(body)) - positions.append(observer.next_setting(body)) - except (ephem.NeverUpError, ephem.AlwaysUpError): - try: - if ( - ephem.localtime(observer.previous_transit(body)).date() - == ephem.localtime(observer.date).date() - and observer.previous_transit(body) < observer.date - ): - positions.append("-") - positions.append(observer.previous_transit(body)) - positions.append("-") - elif ( - ephem.localtime(observer.previous_transit(body)).date() - == ephem.localtime(observer.date).date() - and observer.next_transit(body) > observer.date - ): - positions.append("-") - positions.append(observer.next_transit(body)) - positions.append("-") - else: - positions.append("-") - positions.append("-") - positions.append("-") - except (ephem.NeverUpError, ephem.AlwaysUpError): - positions.append("-") - positions.append("-") - positions.append("-") - if positions[0] != "-": - positions[0] = ephem.localtime(positions[0]).strftime("%H:%M:%S") - if positions[1] != "-": - positions[1] = ephem.localtime(positions[1]).strftime("%H:%M:%S") - if positions[2] != "-": - positions[2] = ephem.localtime(positions[2]).strftime("%H:%M:%S") - return positions - - -class Moon(object): - """ - Show some infomation about moon - """ - - def __init__(self, lat: str, lon: str, elevation: float) -> None: - self.home = ephem.Observer() - self.home.lat = lat - self.home.lon = lon - self.home.elevation = elevation - # update time - self.home.date = datetime.datetime.utcnow() - - @property - def get_next_full(self) -> str: - """ - Get the time of the next full moon - Args : None - Returns : str - """ - return ephem.localtime(ephem.next_full_moon(self.home.date)).date() - - @property - def get_net_new(self) -> str: - """ - Get the time of the next new moon - Args : None - Returns : str - """ - return ephem.localtime(ephem.next_new_moon(self.home.date)).date() - - @property - def get_previous_full(self) -> str: - """ - Get the time of the last full moon - Args : None - Returns : str - """ - return ephem.localtime(ephem.previous_full_moon(self.home.date)).date() - - @property - def get_previous_new(self) -> str: - """ - Get the time of the last new moon - Args : None - Returns : str - """ - return ephem.localtime(ephem.previous_new_moon(self.home.date)).date() - - @property - def get_next_last_quarter(self) -> str: - """ - Get the time of the last quarter month - Args : None - Returns : str - """ - return ephem.localtime(ephem.next_last_quarter_moon(self.home.date)).date() - - @property - def get_next_first_quarter(self) -> str: - """ - Get the time of the first quarter month - Args : None - Returns : str - """ - return ephem.localtime(ephem.next_first_quarter_moon(self.home.date)).date() - - @property - def get_previous_last_quarter(self) -> str: - """ - Get the time of the previous last quarter month - Args : None - Returns : str - """ - return ephem.localtime(ephem.previous_last_quarter_moon(self.home.date)).date() - - @property - def get_previous_first_quarter(self) -> str: - """ - Get the time of the previous first quarter month - Args : None - Returns : str - """ - return ephem.localtime(ephem.previous_first_quarter_moon(self.home.date)).date() - - def get_moon_phase(self) -> str: - """ - Get the current moon phase - Args : None - Returns : str - """ - - target_date_local = ephem.localtime(self.home.date).date() - - next_full = self.get_next_full - next_new = self.get_net_new - next_last_quarter = self.get_next_last_quarter - next_first_quarter = self.get_next_first_quarter - previous_full = self.get_previous_full - previous_new = self.get_previous_new - previous_last_quarter = self.get_previous_last_quarter - previous_first_quarter = self.get_previous_first_quarter - - if target_date_local in (next_full, previous_full): - return "Full" - elif target_date_local in (next_new, previous_new): - return "New" - elif target_date_local in (next_first_quarter, previous_first_quarter): - return "First Quarter" - elif target_date_local in (next_last_quarter, previous_last_quarter): - return "Last Quarter" - elif ( - previous_new < next_first_quarter < next_full < next_last_quarter < next_new - ): - return "Waxing Crescent" - elif ( - previous_first_quarter - < next_full - < next_last_quarter - < next_new - < next_first_quarter - ): - return "Waxing Gibbous" - elif ( - previous_full - < next_last_quarter - < next_new - < next_first_quarter - < next_full - ): - return "Waning Gibbous" - elif ( - previous_last_quarter - < next_new - < next_first_quarter - < next_full - < next_last_quarter - ): - return "Waning Crescent" - - def get_moon_ra(self) -> str: - """ - Get the current RA of the moon - Args : None - Returns : str - """ - return ephem.Moon(self.home).ra - - def get_moon_dec(self) -> str: - """ - Get the current DEC of the moon - Args : None - Returns : str - """ - return ephem.Moon(self.home).dec - - def get_moon_az(self) -> str: - """ - Get the current AZ of the moon - Args : None - Returns : str - """ - return numpy.degrees(ephem.Moon(self.home).az) - - def get_moon_dec(self) -> str: - """ - Get the current ALT of the moon - Args : None - Returns : str - """ - return numpy.degrees(ephem.Moon(self.home).alt) - - def get_moon_rise(self) -> str: - """ - Get the rise time of the moon - Args : None - Returns : str - """ - return parser(self.home, ephem.Moon(self.home))[0] - - def get_moon_set(self) -> str: - """ - Get the set time of the moon - Args : None - Returns : str - """ - return parser(self.home, ephem.Moon(self.home))[2] - - def get_moon_transmit(self) -> str: - """ - Get the transmit time of the moon - Args : None - Returns : str - """ - return parser(self.home, ephem.Moon(self.home))[1] - - -class Sun(object): - """ - Get some infomation about sun - """ - - def __init__(self, lat: str, lon: str, elevation: float) -> None: - self.home = ephem.Observer() - self.home.lat = lat - self.home.lon = lon - self.home.elevation = elevation - # update time - self.home.date = datetime.datetime.utcnow() - - @property - def get_astro_twilight_start(self) -> str: - """ - Get the start time of astronomical twilight - Args : None - Returns : str - """ - return self.get_sun_twilights(self.home)[2][0] - - @property - def get_astro_twilight_stop(self) -> str: - """ - Get the stop time of astronomical twilight - Args : None - Returns : str - """ - return self.get_sun_twilights(self.home)[2][1] - - @property - def get_civil_twilight_start(self) -> str: - """ - Get the start time of civil twilight - Args : None - Returns : str - """ - return self.get_sun_twilights(self.home)[0][0] - - @property - def get_civil_twilight_stop(self) -> str: - """ - Get the stop time of civil twilight - Args : None - Returns : str - """ - return self.get_sun_twilights(self.home)[0][1] - - def get_sun_twilights(self) -> list: - """ - Get the sun-twilights for the given observer - Args : - observer : ephem.Observer - Returns : list - """ - results = [] - # remember entry observer horizon - observer_horizon = self.home.horizon - # Twilights, their horizons and whether to use the centre of the Sun or not - twilights = [("-6", True), ("-12", True), ("-18", True)] - for twi in twilights: - self.home.horizon = twi[0] - try: - rising_setting = parser(self.home, ephem.Sun(self.home)) - results.append((rising_setting[0], rising_setting[2])) - except ephem.AlwaysUpError: - results.append(("n/a", "n/a")) - # reset observer horizon to entry - self.home.horizon = observer_horizon - return results - - def get_sun_ra(self) -> str: - """ - Get the current RA of the sun - Args : None - Returns : str - """ - return ephem.Sun(self.home).ra - - def get_sun_dec(self) -> str: - """ - Get the current DEC of the sun - Args : None - Returns : str - """ - return ephem.Sun(self.home).dec - - def get_sun_az(self) -> str: - """ - Get the current AZ of the sun - Args : None - Returns : str - """ - return numpy.degrees(ephem.Sun(self.home).az) - - def get_sun_dec(self) -> str: - """ - Get the current ALT of the sun - Args : None - Returns : str - """ - return numpy.degrees(ephem.Sun(self.home).alt) - - def get_sun_rise(self) -> str: - """ - Get the rise time of the sun - Args : None - Returns : str - """ - return parser(self.home, ephem.Sun(self.home))[0] - - def get_sun_set(self) -> str: - """ - Get the set time of the sun - Args : None - Returns : str - """ - return parser(self.home, ephem.Sun(self.home))[2] - - def get_sun_transmit(self) -> str: - """ - Get the transmit time of the sun - Args : None - Returns : str - """ - return parser(self.home, ephem.Sun(self.home))[1] - - -class OtherPlanet(object): - """ - Show infomation about other planets in our solar system - """ - - def __init__(self, lat: str, lon: str, elevation: float) -> None: - self.home = ephem.Observer() - self.home.lat = lat - self.home.lon = lon - self.home.elevation = elevation - # update time - self.home.date = datetime.datetime.utcnow() - - def mercury(self) -> dict: - """ - Get real-time information of Mercury - Args : None - Returns : dict - """ - return { - "mercury_rise": "%s" % parser(self.home, ephem.Mercury(self.home))[0], - "mercury_transit": "%s" % parser(self.home, ephem.Mercury(self.home))[1], - "mercury_set": "%s" % parser(self.home, ephem.Mercury(self.home))[2], - "mercury_az": "%.2f°" % numpy.degrees(ephem.Mercury(self.home).az), - "mercury_alt": "%.2f°" % numpy.degrees(ephem.Mercury(self.home).alt), - "mercury_ra": "%s" % ephem.Mercury(self.home).ra, - "mercury_dec": "%s" % ephem.Mercury(self.home).dec, - } - - def venus(self) -> dict: - """ - Get real-time information of Venus - Args : None - Returns : dict - """ - return { - "venus_rise": "%s" % parser(self.home, ephem.Venus(self.home))[0], - "venus_transit": "%s" % parser(self.home, ephem.Venus(self.home))[1], - "venus_set": "%s" % parser(self.home, ephem.Venus(self.home))[2], - "venus_az": "%.2f°" % numpy.degrees(ephem.Venus(self.home).az), - "venus_alt": "%.2f°" % numpy.degrees(ephem.Venus(self.home).alt), - "venus_ra": "%s" % ephem.Venus(self.home).ra, - "venus_dec": "%s" % ephem.Venus(self.home).dec, - } - - def Mars(self) -> dict: - """""" - return { - "mars_rise": "%s" % parser(self.home, ephem.Mars(self.home))[0], - "mars_transit": "%s" % parser(self.home, ephem.Mars(self.home))[1], - "mars_set": "%s" % parser(self.home, ephem.Mars(self.home))[2], - "mars_az": "%.2f°" % numpy.degrees(ephem.Mars(self.home).az), - "mars_alt": "%.2f°" % numpy.degrees(ephem.Mars(self.home).alt), - } - - def Jupiter(self) -> dict: - """""" - return { - "jupiter_rise": "%s" % parser(self.home, ephem.Jupiter(self.home))[0], - "jupiter_transit": "%s" % parser(self.home, ephem.Jupiter(self.home))[1], - "jupiter_set": "%s" % parser(self.home, ephem.Jupiter(self.home))[2], - "jupiter_az": "%.2f°" % numpy.degrees(ephem.Jupiter(self.home).az), - "jupiter_alt": "%.2f°" % numpy.degrees(ephem.Jupiter(self.home).alt), - } - - def Saturn(self) -> dict: - """""" - return { - "saturn_rise": "%s" % parser(self.home, ephem.Saturn(self.home))[0], - "saturn_transit": "%s" % parser(self.home, ephem.Saturn(self.home))[1], - "saturn_set": "%s" % parser(self.home, ephem.Saturn(self.home))[2], - "saturn_az": "%.2f°" % numpy.degrees(ephem.Saturn(self.home).az), - "saturn_alt": "%.2f°" % numpy.degrees(ephem.Saturn(self.home).alt), - } - - def Uranus(self) -> dict: - """""" - return { - "uranus_rise": "%s" % parser(self.home, ephem.Uranus(self.home))[0], - "uranus_transit": "%s" % parser(self.home, ephem.Uranus(self.home))[1], - "uranus_set": "%s" % parser(self.home, ephem.Uranus(self.home))[2], - "uranus_az": "%.2f°" % numpy.degrees(ephem.Uranus(self.home).az), - "uranus_alt": "%.2f°" % numpy.degrees(ephem.Uranus(self.home).alt), - } - - def Neptune(self) -> dict: - """""" - return { - "neptune_rise": "%s" % parser(self.home, ephem.Neptune(self.home))[0], - "neptune_transit": "%s" % parser(self.home, ephem.Neptune(self.home))[1], - "neptune_set": "%s" % parser(self.home, ephem.Neptune(self.home))[2], - "neptune_az": "%.2f°" % numpy.degrees(ephem.Neptune(self.home).az), - "neptune_alt": "%.2f°" % numpy.degrees(ephem.Neptune(self.home).alt), - } - - -class NorthPolar(object): - """ - Show infomation about other planets in our solar system - """ - - def __init__(self, lat: str, lon: str, elevation: float) -> None: - self.home = ephem.Observer() - self.home.lat = lat - self.home.lon = lon - self.home.elevation = elevation - # update time - self.home.date = datetime.datetime.utcnow() - - def get_polaris_data(self) -> list: - """ - Get the polar data for a given observer - Args : None - Returns : list - """ - polaris_data = [] - - """ - lst = 100.46 + 0.985647 * d + lon + 15 * ut [based on http://www.stargazing.net/kepler/altaz.html] - d - the days from J2000 (1200 hrs UT on Jan 1st 2000 AD), including the fraction of a day - lon - your longitude in decimal degrees, East positive - ut - the universal time in decimal hours - """ - - j2000 = ephem.Date("2000/01/01 12:00:00") - d = self.home.date - j2000 - - lon = numpy.rad2deg(float(repr(self.home.lon))) - - utstr = self.home.date.datetime().strftime("%H:%M:%S") - ut = ( - float(utstr.split(":")[0]) - + float(utstr.split(":")[1]) / 60 - + float(utstr.split(":")[2]) / 3600 - ) - - lst = 100.46 + 0.985647 * d + lon + 15 * ut - lst = lst - int(lst / 360) * 360 - - polaris = ephem.readdb("Polaris,f|M|F7,2:31:48.704,89:15:50.72,2.02,2000") - polaris.compute() - polaris_ra_deg = numpy.rad2deg(float(repr(polaris.ra))) - - # Polaris Hour Angle = LST - RA Polaris [expressed in degrees or 15*(h+m/60+s/3600)] - pha = lst - polaris_ra_deg - - # normalize - if pha < 0: - pha += 360 - elif pha > 360: - pha -= 360 - # append polaris hour angle - polaris_data.append(pha) - - # append polaris next transit - try: - polaris_data.append( - ephem.localtime(self.home.next_transit(polaris)).strftime("%H:%M:%S") - ) - except (ephem.NeverUpError, ephem.AlwaysUpError): - polaris_data.append("-") - # append polaris alt - polaris_data.append(polaris.alt) - - return polaris_data - - def get_polar_info(self) -> dict: - """""" - polaris_data = self.get_polaris_data(self.home) - return { - "polaris_hour_angle": polaris_data[0], - "polaris_next_transit": "%s" % polaris_data[1], - "polaris_alt": "%.2f°" % numpy.degrees(polaris_data[2]), - } diff --git a/modules/oxygen_celestial/search.py b/modules/oxygen_celestial/search.py deleted file mode 100644 index a02dff08..00000000 --- a/modules/oxygen_celestial/search.py +++ /dev/null @@ -1,926 +0,0 @@ -# coding=utf-8 - -""" - -Copyright(c) 2022-2023 Max Qian - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License version 3 as published by the Free Software Foundation. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. -You should have received a copy of the GNU Library General Public License -along with this library; see the file COPYING.LIB. If not, write to -the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -Boston, MA 02110-1301, USA. - -""" - -from functools import cached_property -from typing import Generator, List, Tuple, Optional, Union -import json -import numpy as np -import re -import sqlite3 - -from typing import Optional - -from astropy.coordinates import SkyCoord - -class InvalidCoordinates(Exception): - """ - Raised when coordinates are not valid. - - Maybe you're passing an object without registered coordinates (typically an `Unknown` object) - to some function; or you input coordinates as text in a wrong format: to be recognized - the input text must be in the format `HH:MM:SS.ss +/-DD:MM:SS.s`. - """ - def __init__(self, text: Optional[str] = None): - if text is not None: - super().__init__(text) - else: # pragma: no cover - super().__init__('Coordinates not recognized.') - - -class ObjectNotFound(Exception): - """ - Raised when a valid object identifier isn't found in the database. - - The identifier is recognized to be part of one of the supported catalogs, - but the object isn't in the database (or doesn't exist at all). - - For example, `pyongc.Object('NGC7000A')` is valid, but it doesn't exist. - """ - def __init__(self, name: Optional[str] = None): - if name is not None: - super().__init__(f'Object named {name} not found in the database.') - else: # pragma: no cover - super().__init__('Object not found in the database.') - - -class UnknownIdentifier(Exception): - """ - Raised when input text can't be recognized as a valid object identifier. - - You're asking for an identifier using the wrong format, or using an identifier - which refers to a catalog not supported by PyOngc. - """ - def __init__(self, text: Optional[str] = None): - if text is not None: - super().__init__(f'The name "{text}" is not recognized.') - else: # pragma: no cover - super().__init__('Unrecognized object name.') - -DBPATH = "data.db" - -PATTERNS = {'NGC|IC': r'^((?:NGC|IC)\s?)(\d{1,4})\s?((NED)(\d{1,2})|[A-Z]{1,2})?$', - 'Messier': r'^(M\s?)(\d{1,3})$', - 'Barnard': r'^(B\s?)(\d{1,3})$', - 'Caldwell': r'^(C\s?)(\d{1,3})$', - 'Collinder': r'^(CL\s?)(\d{1,3})$', - 'ESO': r'^(ESO\s?)(\d{1,3})-(\d{1,3})$', - 'Harvard': r'^(H\s?)(\d{1,2})$', - 'Hickson': r'^(HCG\s?)(\d{1,3})$', - 'LBN': r'^(LBN\s?)(\d{1,3})$', - 'Melotte': r'^(MEL\s?)(\d{1,3})$', - 'MWSC': r'^(MWSC\s?)(\d{1,4})$', - 'PGC': r'^((?:PGC|LEDA)\s?)(\d{1,6})$', - 'UGC': r'^(UGC\s?)(\d{1,5})$', - } - - -class Object(object): - """Describes a Deep Sky Object from ONGC database. - - Each object of this class has the following read only properties: - - * constellation: The constellation where the object is located. - * coords: Object coordinates in HMS and DMS as numpy array or None. - * dec: Object Declination in a easy to read format as string. - * name: The main identifier of the object. - * ra: Object Right Ascension in a easy to read format as string. - * type: Object type. - - The class also provides the following methods: - - * __init__: Object constructor. - * __str__: Returns a basic description of the object. - * xephemFormat: Returns object data in Xephem format. - - """ - - def __init__(self, name: str, returndup: bool = False): - """Object constructor. - - Args: - name: Object identifier (ex.: 'NGC1', 'M15'). - returndup: If set to True, don't resolve Dup objects. Default is False. - - Raises: - TypeError: If the object identifier is not a string. - pyongc.ObjectNotFound: If the object identifier is not found in the database. - """ - # Make sure user passed a string as parameter - if not isinstance(name, str): - raise TypeError('Wrong type as parameter. A string type was expected.') - - catalog, objectname = _recognize_name(name.upper()) - - cols = ('objects.name, objects.type, objTypes.typedesc, ra, dec, const') - tables = ('objects JOIN objTypes ON objects.type = objTypes.type ' - 'JOIN objIdentifiers ON objects.name = objIdentifiers.name') - if catalog == 'Messier': - params = f'messier="{objectname}"' - else: - params = f'objIdentifiers.identifier="{objectname}"' - objectData = _queryFetchOne(cols, tables, params) - - if objectData is None: - raise ObjectNotFound(objectname) - - # If object is a duplicate then return the main object - if objectData[2] == "Dup" and not returndup: - if objectData[26] != "": - objectname = f'NGC{objectData[26]}' - else: - objectname = f'IC{objectData[27]}' - params = f'objIdentifiers.identifier="{objectname}"' - objectData = _queryFetchOne(cols, tables, params) - - # Assign object properties - self._name = objectData[0] - self._type = objectData[1] - self._ra = objectData[3] - self._dec = objectData[4] - self._const = objectData[5] - - def __str__(self) -> str: - """ - Returns a basic description of the object. - >>> s = Object("ngc1") - >>> print(s) - NGC0001, Galaxy in Peg - """ - return f'{self._name}, {self._type} in {self._const}' - - @cached_property - def constellation(self) -> str: - """ - The constellation where the object is located. - >>> s = Object("ngc1") - >>> s.constellation - 'Peg' - Returns: - Name of the constellation in IAU 3-letter form. - """ - return self._const - - @cached_property - def coords(self) -> Optional[np.ndarray]: - """ - Returns object coordinates in HMS and DMS as numpy array or None. - Returns: - A numpy array of shape (2, 3) with R.A. and Declination - values expressed in HMS and DMS. - """ - if self._ra is None or self._dec is None: - return None - - ra = np.empty(3) - ra[0] = np.trunc(np.rad2deg(self._ra) / 15) - ms = ((np.rad2deg(self._ra) / 15) - ra[0]) * 60 - ra[1] = np.trunc(ms) - ra[2] = (ms - ra[1]) * 60 - - dec = np.empty(3) - dec[0] = np.trunc(np.rad2deg(np.abs(self._dec))) - ms = (np.rad2deg(np.abs(self._dec)) - dec[0]) * 60 - dec[1] = np.trunc(ms) - dec[2] = (ms - dec[1]) * 60 - dec[0] = dec[0] * -1 if np.signbit(self._dec) else dec[0] - return np.array([ra, dec, ]) - - @cached_property - def dec(self) -> str: - """ - Object Declination in a easy to read format as string. - If you need the raw data to use in calculations use `coords` or `rad_coords` properties. - Returns: - string: `'+/-DD:MM:SS.s'` or `'N/A'` if the object has no coordinates. - - """ - if self.coords is not None: - return '{:+03.0f}:{:02.0f}:{:04.1f}'.format(*self.coords[1]) - else: - return 'N/A' - - @cached_property - def name(self) -> str: - """ - The main identifier of the object. - Returns: - The main identifier of the object, as listed in ONGC database - or its addendum. - """ - return self._name - - @cached_property - def ra(self) -> str: - """ - Object Right Ascension in a easy to read format as string. - If you need the raw data to use in calculations use `coords` or `rad_coords` properties. - Returns: - string: `'HH:MM:SS.ss'` or `'N/A'` if the object has no coordinates. - """ - if self.coords is not None: - return '{:02.0f}:{:02.0f}:{:05.2f}'.format(*self.coords[0]) - else: - return 'N/A' - - @cached_property - def rad_coords(self) -> Optional[np.ndarray]: - """ - Returns object coordinates in radians as numpy array or None. - Returns: - A numpy array of shape (2,) with R.A. and Declination - values expressed in radians. - """ - if self._ra is None or self._dec is None: - return None - - return np.array([self._ra, self._dec, ]) - - @cached_property - def type(self) -> str: - """ - Object type. - Returns: - string: Object type - """ - return self._type - - @cached_property - def astropy(self) -> SkyCoord: - """ - Convert to a SkyCoord Instance - SkyCoord('00h42.5m', '+41d12m') - """ - return SkyCoord(f"{int(self.coords[0][0])}h{int(self.coords[0][1])}m{int(self.coords[0][2])}s", - f"{int(self.coords[1][0])}d{int(self.coords[1][1])}m{int(self.coords[1][2])}s" - ) - - def to_json(self) -> str: - """Returns object data in JSON format.""" - return json.dumps(self, cls=ObjectEncoder) - -class ObjectEncoder(json.JSONEncoder): - """ - A custom json.dumps serializer for Object class. - """ - - def default(self, obj: Object) -> dict: - """ - A custom json.dumps serializer for Object class. - Args: - obj: the Object object to encode. - """ - if isinstance(obj, Object): - obj_description = {'name': obj.name, - 'type': obj.type, - 'coordinates': {'RA': obj.ra, - 'DEC': obj.dec, - }, - 'constellation': obj.constellation - } - return obj_description - else: - return super().default(obj) - - -def _distance(coords1: np.ndarray, coords2: np.ndarray) -> Tuple[float, float, float]: - """Calculate distance between two points in the sky. - - With p1 = '01:00:00 +15:30:00' and p2 = '01:30:00 +10:30:00': - - >>> import numpy as np - >>> p1 = np.array([0.26179939, 0.27052603]) - >>> p2 = np.array([0.39269908, 0.18325957]) - >>> _distance(p1, p2) - (8.852139937970884, 7.499999776570824, -4.999999851047216) - - Args: - coords1: R.A. and Dec expressed in radians of the first point as - numpy array with shape(2,) - coords2: R.A. and Dec expressed in radians of the second point as - numpy array with shape(2,) - - Returns: - `(angular separation, difference in A.R, difference in Dec)` - - This function will return three float values, which are the apparent total - angular separation between the two objects, the difference in Right Ascension and the - difference in Declination. - - All values are expressed in degrees. - - """ - a1 = coords1[0] - a2 = coords2[0] - d1 = coords1[1] - d2 = coords2[1] - - # separation = np.arccos(np.sin(d1)*np.sin(d2) + np.cos(d1)*np.cos(d2)*np.cos(a1-a2)) - # Better precision formula - # see http://aa.quae.nl/en/reken/afstanden.html - separation = 2*np.arcsin(np.sqrt(np.sin((d2-d1)/2)**2 + - np.cos(d1)*np.cos(d2)*np.sin((a2-a1)/2)**2)) - - return np.degrees(separation), np.degrees(a2-a1), np.degrees(d2-d1) - - -def _limiting_coords(coords: np.ndarray, radius: int) -> str: - """Write query filters for limiting search to specific area of the sky. - - This is a quick method to exclude objects farther than a specified distance - from the starting point, but it's not meant to be precise. - - >>> from pyongc.ongc import Object, _limiting_coords - >>> start = Object('ngc1').coords - >>> _limiting_coords(start, 2) - ' AND (ra <= 0.06660176425610362 OR ra >= 6.279973901355917) AND \ -(dec BETWEEN 0.44869069854374555 AND 0.5185038686235187)' - - Args: - coords: R.A. and Dec of the starting point in the sky. - - It can be expressed as a numpy array of H:M:S/D:M:S - - `array([[HH., MM., SS.ss],[DD., MM., SS.ss]])` - - or as numpy array of radians - - `array([RA, Dec])` - radius: radius of the search in degrees - - Returns: - Parameters to be added to query - - """ - if coords.shape == (2, 3): - rad_coords = np.empty(2) - rad_coords[0] = np.radians(np.sum(coords[0] * [15, 1/4, 1/240])) - if np.signbit(coords[1][0]): - rad_coords[1] = np.radians(np.sum(coords[1] * [1, -1/60, -1/3600])) - else: - rad_coords[1] = np.radians(np.sum(coords[1] * [1, 1/60, 1/3600])) - else: - rad_coords = coords - - radius_rad = np.radians(radius) - ra_lower_limit = rad_coords[0] - radius_rad - ra_upper_limit = rad_coords[0] + radius_rad - if ra_lower_limit < 0: - ra_lower_limit += 2 * np.pi - params = f' AND (ra <= {ra_upper_limit} OR ra >= {ra_lower_limit})' - elif ra_upper_limit > 2 * np.pi: - ra_upper_limit -= 2 * np.pi - params = f' AND (ra <= {ra_upper_limit} OR ra >= {ra_lower_limit})' - else: - params = f' AND (ra BETWEEN {ra_lower_limit} AND {ra_upper_limit})' - - dec_lower_limit = rad_coords[1] - radius_rad - if dec_lower_limit < -1/2 * np.pi: - dec_lower_limit = -1/2 * np.pi - dec_upper_limit = rad_coords[1] + radius_rad - if dec_upper_limit > 1/2 * np.pi: - dec_upper_limit = 1/2 * np.pi - - params += f' AND (dec BETWEEN {dec_lower_limit} AND {dec_upper_limit})' - return params - - -def _queryFetchOne(cols: str, tables: str, params: str) -> tuple: - """Search one row in database. - - Be sure to use a WHERE clause which is very specific, otherwise the query - will return the first row that matches. - - >>> from pyongc.ongc import _queryFetchOne - >>> cols = 'type' - >>> tables = 'objects' - >>> params = 'name="NGC0001"' - >>> _queryFetchOne(cols, tables, params) - ('G',) - - Args: - cols: the `SELECT` field of the query - tables: the `FROM` field of the query - params: the `WHERE` field of the query - - Returns: - Selected row data from database - - """ - try: - db = sqlite3.connect(f'file:{DBPATH}?mode=ro', uri=True) - except sqlite3.Error: - raise OSError(f'There was a problem accessing database file at {DBPATH}') - - try: - cursor = db.cursor() - cursor.execute(f'SELECT {cols} ' - f'FROM {tables} ' - f'WHERE {params}' - ) - objectData = cursor.fetchone() - except Exception as err: # pragma: no cover - raise err - finally: - db.close() - - return objectData - - -def _queryFetchMany(cols: str, tables: str, params: str, - order: str = '') -> Generator[tuple, None, None]: - """Search many rows in database. - - >>> from pyongc.ongc import _queryFetchMany - >>> cols = 'name' - >>> tables = 'objects' - >>> params = 'type="G"' - >>> _queryFetchMany(cols, tables, params) #doctest: +ELLIPSIS - - - Args: - cols: the `SELECT` field of the query - tables: the `FROM` field of the query - params: the `WHERE` field of the query - order: the `ORDER` clause of the query - - Yields: - Selected row data from database - - """ - try: - db = sqlite3.connect(f'file:{DBPATH}?mode=ro', uri=True) - except sqlite3.Error: - raise OSError(f'There was a problem accessing database file at {DBPATH}') - - try: - cursor = db.cursor() - - cursor.execute(f'SELECT {cols} ' - f'FROM {tables} ' - f'WHERE {params}' - f'{" ORDER BY " + order if order != "" else ""}' - ) - while True: - objectList = cursor.fetchmany() - if objectList == []: - break - yield objectList[0] - except Exception as err: # pragma: no cover - raise err - finally: - db.close() - - -def _recognize_name(text: str) -> Tuple[str, str]: - """Recognize catalog and object id. - - >>> from pyongc.ongc import _recognize_name - >>> _recognize_name('NGC1') - ('NGC|IC', 'NGC0001') - - Args: - text: the object name in input. Must be uppercase. - - Returns: - `('catalog name', 'object name')` - - Raises: - UnknownIdentifier: If the text cannot be recognized as a valid object name. - - """ - for cat, pat in PATTERNS.items(): - name_parts = re.match(pat, text) - if name_parts is not None: - if cat == 'NGC|IC' and name_parts.group(3) is not None: - # User searches for a NGC/IC sub-object - if name_parts.group(4) is not None: - # User searches for a NED suffixed component - objectname = f'{name_parts.group(1).strip()}' \ - f'{name_parts.group(2):0>4}' \ - f' {name_parts.group(4)}' \ - f'{name_parts.group(5):0>2}' - else: - # User searches for a letter suffixed component - objectname = f'{name_parts.group(1).strip()}' \ - f'{name_parts.group(2):0>4}' \ - f'{name_parts.group(3).strip()}' - elif cat in ('NGC|IC', 'MWSC'): - objectname = f'{name_parts.group(1).strip()}{name_parts.group(2):0>4}' - elif cat == 'ESO': - objectname = f'{name_parts.group(1).strip()}{name_parts.group(2):0>3}-' \ - f'{name_parts.group(3):0>3}' - elif cat == 'Harvard': - objectname = f'{name_parts.group(1).strip()}{name_parts.group(2):0>2}' - elif cat == 'Messier': - # We need to return only the numeric part of the name - objectname = ('101' if name_parts.group(2) == '102' - else f'{name_parts.group(2):0>3}' - ) - elif cat == 'UGC': - objectname = f'{name_parts.group(1).strip()}{name_parts.group(2):0>5}' - elif cat == 'PGC': - # Fixed catalog name to recognize also LEDA prefix - objectname = f'{cat}{name_parts.group(2):0>6}' - else: - objectname = f'{name_parts.group(1).strip()}{name_parts.group(2):0>3}' - return cat, objectname - raise UnknownIdentifier(text) - - -def _str_to_coords(text: str) -> np.ndarray: - """Recognize coordinates as string and return them as radians. - - Args: - text (string): a string expressing coordinates in the form `HH:MM:SS.ss +/-DD:MM:SS.s` - - Returns: - `array([RA, Dec])` - - A numpy array of shape (2,) with coordinates expressed in radians. - - Raises: - InvalidCoordinates: If the text cannot be recognized as valid coordinates. - - """ - pattern = re.compile(r'^(?:(\d{1,2}):(\d{1,2}):(\d{1,2}(?:\.\d{1,2})?))\s' - r'(?:([+-]\d{1,2}):(\d{1,2}):(\d{1,2}(?:\.\d{1,2})?))$') - result = pattern.match(text) - - if result: - hms = np.array([float(x) for x in result.groups()[0:3]]) - ra = np.radians(np.sum(hms * [15, 1/4, 1/240])) - dms = np.array([float(x) for x in result.groups()[3:6]]) - if np.signbit(dms[0]): - dec = np.radians(np.sum(dms * [1, -1/60, -1/3600])) - else: - dec = np.radians(np.sum(dms * [1, 1/60, 1/3600])) - - return np.array([ra, dec]) - else: - raise InvalidCoordinates(f'This text cannot be recognized as coordinates: {text}') - - -def get(name: str) -> Optional[Object]: - """Search and return an object from the database. - - If an object name isn't recognized, it will return None. - - Args: - name: the name of the object - - Returns: - Object or None. - - """ - try: - obj = Object(name) - except (ObjectNotFound, UnknownIdentifier): - return None - return obj - - -def getNeighbors(obj: Union[Object, str], separation: Union[int, float], - catalog: str = "all") -> List[Tuple[Object, float]]: - """ - Find all neighbors of an object within a user selected range. - - It requires an object as the starting point of the search (either a string containing - the name or a Object type) and a search radius expressed in arcmins. - - The maximum allowed search radius is 600 arcmin (10 degrees). - - It returns a list of of tuples with the Object objects found in range and its distance, - or an empty list if no object is found: - - >>> from pyongc.ongc import Object, getNeighbors - >>> s1 = Object("ngc521") - >>> getNeighbors(s1, 15) #doctest: +ELLIPSIS - [(, 0.13726168561780452), \ - (, 0.24140243942744602)] - - >>> from pyongc.ongc import getNeighbors - >>> getNeighbors("ngc521", 1) - [] - - The optional "catalog" parameter can be used to filter the search to only NGC or IC objects: - - >>> from pyongc.ongc import getNeighbors - >>> getNeighbors("ngc521", 15, catalog="NGC") #doctest: +ELLIPSIS - [(, 0.24140243942744602)] - - Args: - object: a Object object or a string which identifies the object - separation: maximum distance from the object expressed in arcmin - catalog: filter for "NGC" or "IC" objects - default is all - - Returns: - A list of tuples with each element composed by the Object object found and - its distance from the starting point, ordered by distance. - - Raises: - ValueError: If the search radius exceeds 10 degrees. - InvalidCoordinates: If the starting object hasn't got registered cordinates. - - """ - if not isinstance(obj, Object): - obj = Object(obj) - if separation > 600: - raise ValueError('The maximum search radius allowed is 10 degrees.') - if obj.rad_coords is None: - raise InvalidCoordinates('Starting object hasn\'t got registered coordinates.') - - cols = 'objects.name' - tables = 'objects' - params = f'type != "Dup" AND name !="{obj.name}"' - if catalog.upper() in ["NGC", "IC"]: - params += f' AND name LIKE "{catalog.upper()}%"' - - params += _limiting_coords(obj.rad_coords, np.ceil(separation / 60)) - - neighbors = [] - for item in _queryFetchMany(cols, tables, params): - possibleNeighbor = Object(item[0]) - distance = getSeparation(obj, possibleNeighbor)[0] - if distance <= (separation / 60): - neighbors.append((possibleNeighbor, distance)) - - return sorted(neighbors, key=lambda neighbor: neighbor[1]) - - -def getSeparation(obj1: Union[Object, str], obj2: Union[Object, str], - style: str = "raw") -> Union[Tuple[float, float, float], str]: - """Finds the apparent angular separation between two objects. - - This function will compute the apparent angular separation between two objects, - either identified with their names as strings or directly as Object type. - - By default it returns a tuple containing the angular separation and the differences in A.R. - and Declination expressed in degrees: - - >>> from pyongc.ongc import Object, getSeparation - >>> s1 = Object("ngc1") - >>> s2 = Object("ngc2") - >>> getSeparation(s1, s2) - (0.03008927371519897, 0.005291666666666788, -0.02972222222221896) - - >>> from pyongc.ongc import getSeparation - >>> getSeparation("ngc1", "ngc2") - (0.03008927371519897, 0.005291666666666788, -0.02972222222221896) - - With the optional parameter `style` set to `text`, it returns a formatted string: - - >>> from pyongc.ongc import getSeparation - >>> getSeparation("ngc1", "ngc2", style="text") - '0° 1m 48.32s' - - If one of the objects is not found in the database it returns an ObjectNotFound exception: - - >>> from pyongc.ongc import getSeparation - >>> getSeparation("ngc1a", "ngc2") - Traceback (most recent call last): - ... - pyongc.exceptions.ObjectNotFound: Object named NGC0001A not found in the database. - - Args: - obj1: first Object object or string identifier - obj2: second Object object or string identifier - style: use "text" to return a string with degrees, minutes and seconds - - Returns: - By default the return value is a tuple with values expressed in degrees - - (angular separation, difference in A.R, difference in Dec) - - With the `style` parameter set to `text` we get a more readable output in the form - `'DD° MMm SS.SSs'` - - """ - if not isinstance(obj1, Object): - obj1 = Object(obj1) - if not isinstance(obj2, Object): - obj2 = Object(obj2) - if obj1.rad_coords is None or obj2.rad_coords is None: - raise InvalidCoordinates('One object hasn\'t got registered coordinates.') - - separation = _distance(obj1.rad_coords, obj2.rad_coords) - - if style == "text": - d = int(separation[0]) - md = abs(separation[0] - d) * 60 - m = int(md) - s = (md - m) * 60 - return f'{d:d}° {m:d}m {s:.2f}s' - else: - return separation - - -def listObjects(**kwargs) -> List[Object]: - """ - Query the database for DSObjects with specific parameters. - - This function returns a list of all DSObjects that match user defined parameters. - If no argument is passed to the function, it returns all the objects from the database: - - >>> from pyongc.ongc import listObjects - >>> objectList = listObjects() - >>> len(objectList) - 13992 - - Filters are combined with "AND" in the query; only one value for filter is allowed: - - >>> from pyongc.ongc import listObjects - >>> objectList = listObjects(catalog="NGC", constellation=["Boo", ]) - >>> len(objectList) - 281 - - Duplicated objects are not resolved to main objects: - - >>> from pyongc.ongc import listObjects - >>> objectList = listObjects(type=["Dup", ]) - >>> print(objectList[0]) - IC0011, Duplicated record in Cas - - The maxSize filter will include objects with no size recorded in database: - - >>> from pyongc.ongc import listObjects - >>> objectList = listObjects(maxsize=0) - >>> len(objectList) - 1967 - - Args: - catalog (string, optional): filter for catalog. [NGC|IC|M] - type (list, optional): filter for object type. See OpenNGC types list. - constellation (list, optional): filter for constellation - (three letter latin form - e.g. "And") - minsize (float, optional): filter for objects with MajAx >= minSize(arcmin) - maxsize (float, optional): filter for objects with MajAx < maxSize(arcmin) - OR MajAx not available - uptobmag (float, optional): filter for objects with B-Mag brighter than value - uptovmag (float, optional): filter for objects with V-Mag brighter than value - minra (float, optional): filter for objects with RA degrees greater than value - maxra (float, optional): filter for objects with RA degrees lower than value - mindec (float, optional): filter for objects above specified Dec degrees - maxdec (float, optional): filter for objects below specified Dec degrees - cname (string, optional): filter for objects with common name like input value - withname (bool, optional): filter for objects with common names - - Returns: - A list of ongc.Object objects. - - Raises: - ValueError: If a filter name other than those expected is inserted. - ValueError: If an unrecognized catalog name is entered. Only [NGC|IC|M] are permitted. - - """ - available_filters = ['catalog', - 'type', - 'constellation', - 'minsize', - 'maxsize', - 'uptobmag', - 'uptovmag', - 'minra', - 'maxra', - 'mindec', - 'maxdec', - 'cname', - 'withname'] - cols = 'objects.name' - tables = 'objects' - - if kwargs == {}: - params = '1' - return [Object(str(item[0]), True) for item in _queryFetchMany(cols, tables, params)] - for element in kwargs: - if element not in available_filters: - raise ValueError("Wrong filter name.") - - paramslist = [] - order = '' - if "catalog" in kwargs: - if kwargs["catalog"].upper() == "NGC" or kwargs["catalog"].upper() == "IC": - paramslist.append(f'name LIKE "{kwargs["catalog"].upper()}%"') - elif kwargs["catalog"].upper() == "M": - paramslist.append('messier != ""') - order = 'messier ASC' - else: - raise ValueError('Wrong value for catalog filter. [NGC|IC|M]') - if "type" in kwargs: - types = [f'"{t}"' for t in kwargs["type"]] - paramslist.append(f'type IN ({",".join(types)})') - - if "constellation" in kwargs: - constellations = [f'"{c.capitalize()}"' for c in kwargs["constellation"]] - paramslist.append(f'const IN ({",".join(constellations)})') - - if "minra" in kwargs and "maxra" in kwargs: - if kwargs["maxra"] > kwargs["minra"]: - paramslist.append(f'ra BETWEEN ' - f'{np.radians(kwargs["minra"])} ' - f'AND {np.radians(kwargs["maxra"])}' - ) - else: - paramslist.append(f'ra >= {np.radians(kwargs["minra"])} ' - f'OR ra <= {np.radians(kwargs["maxra"])}' - ) - elif "minra" in kwargs: - paramslist.append(f'ra >= {np.radians(kwargs["minra"])}') - elif "maxra" in kwargs: - paramslist.append(f'ra <= {np.radians(kwargs["maxra"])}') - - if "mindec" in kwargs and "maxdec" in kwargs: - if kwargs["maxdec"] > kwargs["mindec"]: - paramslist.append(f'dec BETWEEN ' - f'{np.radians(kwargs["mindec"])} ' - f'AND {np.radians(kwargs["maxdec"])}' - ) - elif "mindec" in kwargs: - paramslist.append(f'dec >= {np.radians(kwargs["mindec"])}') - elif "maxdec" in kwargs: - paramslist.append(f'dec <= {np.radians(kwargs["maxdec"])}') - - params = " AND ".join(paramslist) - return [Object(item[0], True) for item in _queryFetchMany(cols, tables, params, order)] - - -def nearby(coords_string: str, separation: float = 60, - catalog: str = "all") -> List[Tuple[Object, float]]: - """ - Search for objects around given coordinates. - - Returns all objects around a point expressed by the coords parameter and within a search - radius expressed by the separation parameter. - Coordinates must be Right Ascension and Declination expressed as a string in the - form "HH:MM:SS.ss +/-DD:MM:SS.s". - - The maximum allowed search radius is 600 arcmin (10 degrees) and default value is 60. - - It returns a list of of tuples with the Object objects found in range and its distance, - or an empty list if no object is found: - - >>> from pyongc.ongc import nearby - >>> nearby('11:08:44 -00:09:01.3') #doctest: +ELLIPSIS +FLOAT_CMP - [(, 0.1799936868460791), \ - (, 0.7398295985600021), \ - (, 0.9810037613087355)] - - The optional "catalog" parameter can be used to filter the search to only NGC or IC objects: - - >>> from pyongc.ongc import nearby - >>> nearby('11:08:44 -00:09:01.3', separation=60, catalog='NGC') #doctest: +ELLIPSIS \ - +FLOAT_CMP - [(, 0.7398295985600021)] - - Args: - coords: R.A. and Dec of search center - separation: search radius expressed in arcmin - default 60 - catalog: filter for "NGC" or "IC" objects - default is all - - Returns: - `[(Object, separation),]` - - A list of tuples with the Object object found and its distance from the starting point, - ordered by distance. - - Raises: - ValueError: If the search radius exceeds 10 degrees. - - """ - if separation > 600: - raise ValueError('The maximum search radius allowed is 10 degrees.') - - coords = _str_to_coords(coords_string) - - cols = 'objects.name' - tables = 'objects' - params = 'type != "Dup"' - if catalog.upper() in ["NGC", "IC"]: - params += f' AND name LIKE "{catalog.upper()}%"' - - params += _limiting_coords(coords, np.ceil(separation / 60)) - - neighbors = [] - for item in _queryFetchMany(cols, tables, params): - possibleNeighbor = Object(item[0]) - distance = _distance(coords, possibleNeighbor.rad_coords)[0] - if distance <= (separation / 60): - neighbors.append((possibleNeighbor, distance)) - - return sorted(neighbors, key=lambda neighbor: neighbor[1]) diff --git a/modules/oxygen_celestial/time.py b/modules/oxygen_celestial/time.py deleted file mode 100644 index 6b077cf1..00000000 --- a/modules/oxygen_celestial/time.py +++ /dev/null @@ -1,49 +0,0 @@ -# coding=utf-8 - -""" - -Copyright(c) 2022-2023 Max Qian - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License version 3 as published by the Free Software Foundation. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. -You should have received a copy of the GNU Library General Public License -along with this library; see the file COPYING.LIB. If not, write to -the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -Boston, MA 02110-1301, USA. - -""" - -import time -import subprocess - -def set_ntp(enabled : bool) -> bool: - """ - Set whether to allow time synchronization from the Internet - Args: - enabled : bool - Returns : bool - """ - return subprocess.run(['sudo', 'timedatectl', 'set-ntp', 'true' if enabled else 'false']).returncode == 0 - -def get_timestamp() -> dict: - """ - Get the current timestamp - Returns : dict - utc_timestamp : float - """ - return { 'utc_timestamp': time.time() } - -def set_timestamp(timestamp) -> dict | None: - """ - Set current time - """ - timestamp = int(timestamp) - if not set_ntp(False) or not subprocess.run(['sudo', 'timedatectl', 'set-time', '@{}'.format(timestamp)]).returncode == 0 \ - or not subprocess.run(['sudo', 'date', '-s', '@{}'.format(timestamp)]).returncode == 0: - return - return get_timestamp() diff --git a/modules/oxygen_celestial/vgps.py b/modules/oxygen_celestial/vgps.py deleted file mode 100644 index d307a452..00000000 --- a/modules/oxygen_celestial/vgps.py +++ /dev/null @@ -1,150 +0,0 @@ -# coding=utf-8 - -""" - -Copyright(c) 2022-2023 Max Qian - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License version 3 as published by the Free Software Foundation. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. -You should have received a copy of the GNU Library General Public License -along with this library; see the file COPYING.LIB. If not, write to -the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -Boston, MA 02110-1301, USA. - -""" - -import os, sys, signal, time, datetime, configparser, re - -config_file = "location.conf" -virtualgps_dev = "/tmp/vgps" - - -def convert_to_sexagesimal(coord): - """ - Convert a string of coordinates using delimiters for minutes ('), - seconds (") and degrees (º). It also supports colon (:). - >>> from virtualgps import convert_to_sexagesimal - >>> convert_to_sexagesimal(u"52:08.25:1.5\"") - 52.13791666666667 - >>> convert_to_sexagesimal(u"52:08:16.5\"") - 52.13791666666667 - >>> convert_to_sexagesimal(u"52.1:02:16.5\"") - 52.13791666666667 - >>> convert_to_sexagesimal(u"52º08'16.5\"") - 52.13791666666667 - :param coord: Coordinates in string representation - :return: Coordinates in float representation - """ - elements = re.split(r'[\u00ba\':\"]', coord) - - degrees = float(elements[0]) - if (len(elements) - 1) > 0: - # Convert minutes to degrees - degrees += float(elements[1]) / 60 - if (len(elements) - 1) > 1: - # Convert seconds to degrees - degrees += float(elements[2]) / 3600 - return degrees - - -def nmea_checksum(sentence): - chsum = 0 - for s in sentence: - chsum ^= ord(s) - return hex(chsum)[2:] - -def shutdown(): - try: - os.remove(virtualgps_dev) - except: - pass - os.close(master) - os.close(slave) - sys.exit() - -def term_handler(signum, frame): - raise KeyboardInterrupt - -# register term handler -signal.signal(signal.SIGTERM, term_handler) - -if __name__ == '__main__': - # create pseudo terminal device - master, slave = os.openpty() - pty = os.ttyname(slave) - - # remove leftovers before setting virtual dev - if os.path.isfile(virtualgps_dev): - os.remove(virtualgps_dev) - - os.symlink(pty,virtualgps_dev) - - # load location data from config - if os.path.isfile(config_file): - config = configparser.ConfigParser() - config.read(config_file) - if 'latitude' in config['default'] and 'longitude' in config['default'] and 'elevation' in config['default']: - latitude = convert_to_sexagesimal(config['default']['latitude']) - longitude = convert_to_sexagesimal(config['default']['longitude']) - elevation = float(config['default']['elevation']) - else: - # if config wrong exit - raise KeyboardInterrupt - else: - # if config does not exist exit - raise KeyboardInterrupt - - # W or E - if latitude > 0: - NS = 'N' - else: - NS = 'S' - - # N or S - if longitude > 0: - WE = 'E' - else: - WE = 'W' - - # format for NMEA - latitude = abs(latitude) - longitude = abs(longitude) - lat_deg = int(latitude) - lon_deg = int(longitude) - lat_min = (latitude - lat_deg) * 60 - lon_min = (longitude - lon_deg) * 60 - latitude = "%02d%07.4f" % (lat_deg, lat_min) - longitude = "%03d%07.4f" % (lon_deg, lon_min) - while True: - try: - now = datetime.datetime.utcnow() - date_now = now.strftime("%d%m%y") - time_now = now.strftime("%H%M%S") - - # NMEA minimal sequence: - #$GPGGA,231531.521,5213.788,N,02100.712,E,1,12,1.0,0.0,M,0.0,M,,*6A - #$GPGSA,A,1,,,,,,,,,,,,,1.0,1.0,1.0*30 - #$GPRMC,231531.521,A,5213.788,N,02100.712,E,,,261119,000.0,W*72 - - # assemble nmea sentences - gpgga = "GPGGA,%s,%s,%s,%s,%s,1,12,1.0,%s,M,0.0,M,," % (time_now, latitude, NS, longitude, WE, elevation) - gpgsa = "GPGSA,A,3,,,,,,,,,,,,,1.0,1.0,1.0" - gprmc = "GPRMC,%s,A,%s,%s,%s,%s,,,%s,000.0,W" % (time_now, latitude, NS, longitude, WE, date_now) - - # add nmea checksums - gpgga = "$%s*%s\n" % (gpgga, nmea_checksum(gpgga)) - gpgsa = "$%s*%s\n" % (gpgsa, nmea_checksum(gpgsa)) - gprmc = "$%s*%s\n" % (gprmc, nmea_checksum(gprmc)) - - os.write(master, gpgga.encode()) - os.write(master, gpgsa.encode()) - os.write(master, gprmc.encode()) - - time.sleep(1) - except KeyboardInterrupt: - shutdown() \ No newline at end of file diff --git a/modules/oxygen_celestial/view.sql b/modules/oxygen_celestial/view.sql deleted file mode 100644 index b33ffcc6..00000000 --- a/modules/oxygen_celestial/view.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * from objects \ No newline at end of file From 28a71865782936910ee338f2a5a66ecd7c959723 Mon Sep 17 00:00:00 2001 From: Max Qian Date: Thu, 7 Dec 2023 13:49:57 +0800 Subject: [PATCH 3/9] update --- .gitmodules | 3 +++ modules | 1 + 2 files changed, 4 insertions(+) create mode 160000 modules diff --git a/.gitmodules b/.gitmodules index ddb22ab7..8d4d5bc0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libs"] path = libs url = https://github.com/ElementAstro/LithiumLibrary.git +[submodule "modules"] + path = modules + url = https://github.com/ElementAstro/LithiumModules.git diff --git a/modules b/modules new file mode 160000 index 00000000..0ae8c516 --- /dev/null +++ b/modules @@ -0,0 +1 @@ +Subproject commit 0ae8c516c999059cad17a6bb5704d9fbda50fea2 From 6ed85dcc1927979a27fbae3f224fc3587ad60503 Mon Sep 17 00:00:00 2001 From: Max Qian <64824374+AstroAir@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:53:13 +0000 Subject: [PATCH 4/9] update for a test --- CMakeLists.txt | 5 - libs | 2 +- locale/lithium.pot | 40 +- locale/po/en_US.UTF-8/lithium.po | 38 +- src/ErrorHandler.cpp | 167 +- src/LithiumApp.cpp | 21 +- src/atom/CMakeLists.txt | 1 + src/atom/plugin/module_loader.hpp | 20 +- src/atom/type/CMakeLists.txt | 114 + src/atom/type/cmake/tinyxml2-config.cmake | 57 + src/atom/type/cmake/tinyxml2.pc.in | 10 + src/atom/type/tinyxml2.cpp | 3031 +++++++++++++++++++++ src/atom/type/tinyxml2.h | 2387 ++++++++++++++++ src/controller/AsyncModuleController.hpp | 1 + src/core/plugin/CMakeLists.txt | 2 + src/core/plugin/plugin_info.cpp | 126 + src/core/plugin/plugin_info.hpp | 155 ++ src/core/plugin/wrapper_plugin.cpp | 13 + src/core/plugin/wrapper_plugin.hpp | 40 + src/device/device_manager.cpp | 3 +- tools/CMakeLists.txt | 4 +- tools/csv2json.cpp | 33 +- tools/json2ini.cpp | 34 +- tools/json2xml.cpp | 73 +- tools/xml2json.cpp | 64 +- 25 files changed, 6189 insertions(+), 252 deletions(-) create mode 100644 src/atom/type/CMakeLists.txt create mode 100644 src/atom/type/cmake/tinyxml2-config.cmake create mode 100644 src/atom/type/cmake/tinyxml2.pc.in create mode 100755 src/atom/type/tinyxml2.cpp create mode 100755 src/atom/type/tinyxml2.h create mode 100644 src/core/plugin/plugin_info.cpp create mode 100644 src/core/plugin/plugin_info.hpp create mode 100644 src/core/plugin/wrapper_plugin.cpp create mode 100644 src/core/plugin/wrapper_plugin.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e6bf43a5..48b87b15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -273,11 +273,6 @@ target_link_libraries(lithium_server ${CFITSIO_LIBRARIES}) target_link_libraries(lithium_server OpenSSL::SSL OpenSSL::Crypto) target_link_libraries(lithium_server ${ZLIB_LIBRARIES}) target_link_libraries(lithium_server libzip::zip) -if(WIN32) -target_link_libraries(lithium_server pugixml-shared) -else() -target_link_libraries(lithium_server pugixml-shared) -endif() target_link_libraries(lithium_server sqlite3) target_link_libraries(lithium_server cpp_httplib) target_link_libraries(lithium_server backward) diff --git a/libs b/libs index baf0cf60..ea669667 160000 --- a/libs +++ b/libs @@ -1 +1 @@ -Subproject commit baf0cf6048a76668c8401594a6962bd693419057 +Subproject commit ea66966758399823a75c02308a2fd7bb602c6610 diff --git a/locale/lithium.pot b/locale/lithium.pot index d2f35926..2dd7b049 100644 --- a/locale/lithium.pot +++ b/locale/lithium.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: astro_air@126.com\n" -"POT-Creation-Date: 2023-12-01 13:45+0000\n" +"POT-Creation-Date: 2023-12-07 06:05+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: /workspaces/Lithium/src/device/device_manager.cpp:161 +#: /workspaces/Lithium/src/device/device_manager.cpp:162 msgid "A device with name {} already exists, please choose a different name" msgstr "" @@ -72,34 +72,54 @@ msgstr "" msgid "Set server port to %d" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:80 +#: /workspaces/Lithium/src/LithiumApp.cpp:84 msgid "Failed to load Lithium App , error : {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:97 +#: /workspaces/Lithium/src/LithiumApp.cpp:120 msgid "Get config value: {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:103 +#: /workspaces/Lithium/src/LithiumApp.cpp:126 msgid "Set {} to {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:301 +#: /workspaces/Lithium/src/LithiumApp.cpp:344 msgid "Failed to run chai command : {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:319 +#: /workspaces/Lithium/src/LithiumApp.cpp:362 msgid "Failed to run chai multi command {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:332 +#: /workspaces/Lithium/src/LithiumApp.cpp:375 msgid "Failed to load chaiscript file {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:345 +#: /workspaces/Lithium/src/LithiumApp.cpp:388 msgid "Failed to unload chaiscript file {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:358 +#: /workspaces/Lithium/src/LithiumApp.cpp:401 msgid "Failed to run chai script {}" msgstr "" + +#: /workspaces/Lithium/src/LithiumApp.cpp:423 +msgid "Failed to load module {} in {}" +msgstr "" + +#: /workspaces/Lithium/src/LithiumApp.cpp:442 +msgid "Failed to unload module {}" +msgstr "" + +#: /workspaces/Lithium/src/LithiumApp.cpp:464 +msgid "Failed to reload module {}" +msgstr "" + +#: /workspaces/Lithium/src/LithiumApp.cpp:502 +msgid "Failed to enable module {}" +msgstr "" + +#: /workspaces/Lithium/src/LithiumApp.cpp:521 +msgid "Failed to disable module {}" +msgstr "" diff --git a/locale/po/en_US.UTF-8/lithium.po b/locale/po/en_US.UTF-8/lithium.po index 0c0050fc..9bec94c9 100644 --- a/locale/po/en_US.UTF-8/lithium.po +++ b/locale/po/en_US.UTF-8/lithium.po @@ -1,4 +1,4 @@ -#: /workspaces/Lithium/src/device/device_manager.cpp:161 +#: /workspaces/Lithium/src/device/device_manager.cpp:162 msgid "A device with name {} already exists, please choose a different name" msgstr "" @@ -53,34 +53,54 @@ msgstr "" msgid "Set server port to %d" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:80 +#: /workspaces/Lithium/src/LithiumApp.cpp:84 msgid "Failed to load Lithium App , error : {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:97 +#: /workspaces/Lithium/src/LithiumApp.cpp:120 msgid "Get config value: {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:103 +#: /workspaces/Lithium/src/LithiumApp.cpp:126 msgid "Set {} to {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:301 +#: /workspaces/Lithium/src/LithiumApp.cpp:344 msgid "Failed to run chai command : {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:319 +#: /workspaces/Lithium/src/LithiumApp.cpp:362 msgid "Failed to run chai multi command {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:332 +#: /workspaces/Lithium/src/LithiumApp.cpp:375 msgid "Failed to load chaiscript file {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:345 +#: /workspaces/Lithium/src/LithiumApp.cpp:388 msgid "Failed to unload chaiscript file {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:358 +#: /workspaces/Lithium/src/LithiumApp.cpp:401 msgid "Failed to run chai script {}" msgstr "" + +#: /workspaces/Lithium/src/LithiumApp.cpp:423 +msgid "Failed to load module {} in {}" +msgstr "" + +#: /workspaces/Lithium/src/LithiumApp.cpp:442 +msgid "Failed to unload module {}" +msgstr "" + +#: /workspaces/Lithium/src/LithiumApp.cpp:464 +msgid "Failed to reload module {}" +msgstr "" + +#: /workspaces/Lithium/src/LithiumApp.cpp:502 +msgid "Failed to enable module {}" +msgstr "" + +#: /workspaces/Lithium/src/LithiumApp.cpp:521 +msgid "Failed to disable module {}" +msgstr "" diff --git a/src/ErrorHandler.cpp b/src/ErrorHandler.cpp index 03130a8e..79bdddd8 100644 --- a/src/ErrorHandler.cpp +++ b/src/ErrorHandler.cpp @@ -52,168 +52,11 @@ ErrorHandler::handleError(const Status &status, const oatpp::String &message, co if (status.code == 404) { - auto response = ResponseFactory::createResponse(Status::CODE_404, fmt::format(R"( - - - - - - - 404 - - - - - - - - - - )", error->code, error->message)); + std::stringstream htmlStream; + htmlStream << "404

        :(

        Lithium Server seemed to have crashed.We're sorry for the\n"; + htmlStream << "inconvenience. The server will automatically restart in a few minutes.\n"; + htmlStream << fmt::format("We're just collecting some error info.

        0%complete

        QR Code

        For more information about this issue and possible fixes,visit
        https:

        If you call a support person,give them this info:
        Stop Code:{}Message:{}
        ", error->code, error->message); + auto response = ResponseFactory::createResponse(Status::CODE_404, htmlStream.str()); return response; } else diff --git a/src/LithiumApp.cpp b/src/LithiumApp.cpp index 5e9ccd94..a1d2e260 100644 --- a/src/LithiumApp.cpp +++ b/src/LithiumApp.cpp @@ -454,22 +454,19 @@ namespace Lithium { if (m_ModuleLoader->HasModule(name)) { - if(unloadModule(name)) + if (unloadModule(name)) { return loadModule(m_ModuleLoader->GetModulePath(name), name); } } - else - { - LOG_F(ERROR, _("Failed to reload module {}"), name); - json res = { - {"command", __func__}, - {"status", false}, - {"message", _(fmt::format("Failed to reload module {}", name).c_str())}, - {"timestamp", GetTimestampString()}}; - sendJsonMessage("error", res); - return false; - } + LOG_F(ERROR, _("Failed to reload module {}"), name); + json res = { + {"command", __func__}, + {"status", false}, + {"message", _(fmt::format("Failed to reload module {}", name).c_str())}, + {"timestamp", GetTimestampString()}}; + sendJsonMessage("error", res); + return false; } bool LithiumApp::reloadAllModules() diff --git a/src/atom/CMakeLists.txt b/src/atom/CMakeLists.txt index 8e84d063..4db04675 100644 --- a/src/atom/CMakeLists.txt +++ b/src/atom/CMakeLists.txt @@ -5,6 +5,7 @@ CHECK_INCLUDE_FILE(format HAS_STD_FORMAT) add_subdirectory(io) add_subdirectory(log) +add_subdirectory(type) add_subdirectory(web) if(NOT HAS_STD_FORMAT) diff --git a/src/atom/plugin/module_loader.hpp b/src/atom/plugin/module_loader.hpp index 8bd839e9..e4683823 100644 --- a/src/atom/plugin/module_loader.hpp +++ b/src/atom/plugin/module_loader.hpp @@ -163,7 +163,7 @@ namespace Lithium /** * @brief Loads a dynamic module from the given path. * - * This function loads a dynamic module from the given path. If the loading is successful, it returns true and saves the handle to the module in the handles_ map. + * This function loads a dynamic module from the given path. If the loading is successful, it returns true and saves the handle to the module in the modules_ map. * If the loading fails, it returns false and logs an error message. * * @param[in] path The path of the dynamic module to load. @@ -252,18 +252,18 @@ namespace Lithium template T GetFunction(const std::string &name, const std::string &function_name) { - auto handle_it = handles_.find(name); - if (handle_it == handles_.end()) + auto handle_it = modules_.find(name); + if (handle_it == modules_.end()) { - LOG_F(ERROR, "Failed to find module %s", name.c_str()); + LOG_F(ERROR, "Failed to find module {}", name); return nullptr; } - auto func_ptr = reinterpret_cast(LOAD_FUNCTION(handle_it->second, function_name.c_str())); + auto func_ptr = reinterpret_cast(LOAD_FUNCTION(handle_it->second->handle, function_name.c_str())); if (!func_ptr) { - LOG_F(ERROR, "Failed to get symbol %s from module %s: %s", function_name.c_str(), name.c_str(), dlerror()); + LOG_F(ERROR, "Failed to get symbol {} from module {}: {}", function_name, name, dlerror()); return nullptr; } @@ -283,17 +283,17 @@ namespace Lithium std::shared_ptr GetInstance(const std::string &name, const json &config, const std::string &symbol_name) { - auto handle_it = handles_.find(name); - if (handle_it == handles_.end()) + auto handle_it = modules_.find(name); + if (handle_it == modules_.end()) { - LOG_F(ERROR, "Failed to find module %s", name.c_str()); + LOG_F(ERROR, "Failed to find module {}", name); return nullptr; } auto get_instance_func = GetFunction (*)(const json &)>(name, symbol_name); if (!get_instance_func) { - LOG_F(ERROR, "Failed to get symbol %s from module %s: %s", symbol_name.c_str(), name.c_str(), dlerror()); + LOG_F(ERROR, "Failed to get symbol {} from module {}: {}", symbol_name, name, dlerror()); return nullptr; } diff --git a/src/atom/type/CMakeLists.txt b/src/atom/type/CMakeLists.txt new file mode 100644 index 00000000..68beac4b --- /dev/null +++ b/src/atom/type/CMakeLists.txt @@ -0,0 +1,114 @@ +cmake_minimum_required(VERSION 3.15) +project(tinyxml2 VERSION 9.0.0) + +## +## Honor tinyxml2_SHARED_LIBS to match install interface +## + +if (DEFINED tinyxml2_SHARED_LIBS) + set(BUILD_SHARED_LIBS "${tinyxml2_SHARED_LIBS}") +endif () + +## +## Main library build +## + +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN YES) + +add_library(tinyxml2 tinyxml2.cpp tinyxml2.h) +add_library(tinyxml2::tinyxml2 ALIAS tinyxml2) + +# Uncomment the following line to require C++11 (or greater) to use tinyxml2 +# target_compile_features(tinyxml2 PUBLIC cxx_std_11) +target_include_directories(tinyxml2 PUBLIC "$") + +target_compile_definitions( + tinyxml2 + PUBLIC $<$:TINYXML2_DEBUG> + INTERFACE $<$:TINYXML2_IMPORT> + PRIVATE $<$:_CRT_SECURE_NO_WARNINGS> + PUBLIC _FILE_OFFSET_BITS=64 +) + +set_target_properties( + tinyxml2 + PROPERTIES + DEFINE_SYMBOL "TINYXML2_EXPORT" + VERSION "${tinyxml2_VERSION}" + SOVERSION "${tinyxml2_VERSION_MAJOR}" +) + +## +## Installation +## + +## Standard modules +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +## Custom locations +set(tinyxml2_INSTALL_PKGCONFIGDIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig" + CACHE PATH "Directory for pkgconfig files") + +set(tinyxml2_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/tinyxml2" + CACHE STRING "Path to tinyxml2 CMake files") + +## CMake targets and export scripts + +install( + TARGETS tinyxml2 EXPORT tinyxml2-targets + RUNTIME COMPONENT tinyxml2_runtime + LIBRARY COMPONENT tinyxml2_runtime + NAMELINK_COMPONENT tinyxml2_development + ARCHIVE COMPONENT tinyxml2_development + INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +) + +# Type-specific targets + +if (BUILD_SHARED_LIBS) + set(type shared) +else () + set(type static) +endif () + +install( + EXPORT tinyxml2-targets + DESTINATION "${tinyxml2_INSTALL_CMAKEDIR}" + NAMESPACE tinyxml2:: + FILE tinyxml2-${type}-targets.cmake + COMPONENT tinyxml2_development +) + +# Auto-generated version compatibility file +write_basic_package_version_file( + tinyxml2-config-version.cmake + COMPATIBILITY SameMajorVersion +) + +install( + FILES + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/tinyxml2-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/tinyxml2-config-version.cmake" + DESTINATION "${tinyxml2_INSTALL_CMAKEDIR}" + COMPONENT tinyxml2_development +) + +## Headers + +install( + FILES tinyxml2.h + TYPE INCLUDE + COMPONENT tinyxml2_development +) + +## pkg-config + +configure_file(cmake/tinyxml2.pc.in tinyxml2.pc.gen @ONLY) +file(GENERATE OUTPUT tinyxml2.pc INPUT "${CMAKE_CURRENT_BINARY_DIR}/tinyxml2.pc.gen") +install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/tinyxml2.pc" + DESTINATION "${tinyxml2_INSTALL_PKGCONFIGDIR}" + COMPONENT tinyxml2_development +) diff --git a/src/atom/type/cmake/tinyxml2-config.cmake b/src/atom/type/cmake/tinyxml2-config.cmake new file mode 100644 index 00000000..5baa3641 --- /dev/null +++ b/src/atom/type/cmake/tinyxml2-config.cmake @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.15) + +set(tinyxml2_known_comps static shared) +set(tinyxml2_comp_static NO) +set(tinyxml2_comp_shared NO) +foreach (tinyxml2_comp IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS) + if (tinyxml2_comp IN_LIST tinyxml2_known_comps) + set(tinyxml2_comp_${tinyxml2_comp} YES) + else () + set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE + "tinyxml2 does not recognize component `${tinyxml2_comp}`.") + set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE) + return() + endif () +endforeach () + +if (tinyxml2_comp_static AND tinyxml2_comp_shared) + set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE + "tinyxml2 `static` and `shared` components are mutually exclusive.") + set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE) + return() +endif () + +set(tinyxml2_static_targets "${CMAKE_CURRENT_LIST_DIR}/tinyxml2-static-targets.cmake") +set(tinyxml2_shared_targets "${CMAKE_CURRENT_LIST_DIR}/tinyxml2-shared-targets.cmake") + +macro(tinyxml2_load_targets type) + if (NOT EXISTS "${tinyxml2_${type}_targets}") + set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE + "tinyxml2 `${type}` libraries were requested but not found.") + set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE) + return() + endif () + include("${tinyxml2_${type}_targets}") +endmacro() + +if (tinyxml2_comp_static) + tinyxml2_load_targets(static) +elseif (tinyxml2_comp_shared) + tinyxml2_load_targets(shared) +elseif (DEFINED tinyxml2_SHARED_LIBS AND tinyxml2_SHARED_LIBS) + tinyxml2_load_targets(shared) +elseif (DEFINED tinyxml2_SHARED_LIBS AND NOT tinyxml2_SHARED_LIBS) + tinyxml2_load_targets(static) +elseif (BUILD_SHARED_LIBS) + if (EXISTS "${tinyxml2_shared_targets}") + tinyxml2_load_targets(shared) + else () + tinyxml2_load_targets(static) + endif () +else () + if (EXISTS "${tinyxml2_static_targets}") + tinyxml2_load_targets(static) + else () + tinyxml2_load_targets(shared) + endif () +endif () diff --git a/src/atom/type/cmake/tinyxml2.pc.in b/src/atom/type/cmake/tinyxml2.pc.in new file mode 100644 index 00000000..a4fe22fa --- /dev/null +++ b/src/atom/type/cmake/tinyxml2.pc.in @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: TinyXML2 +Description: simple, small, C++ XML parser +Version: @tinyxml2_VERSION@ +Libs: -L${libdir} -l$ +Cflags: -I${includedir} diff --git a/src/atom/type/tinyxml2.cpp b/src/atom/type/tinyxml2.cpp new file mode 100755 index 00000000..083f54b9 --- /dev/null +++ b/src/atom/type/tinyxml2.cpp @@ -0,0 +1,3031 @@ +/* +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "tinyxml2.h" + +#include // yes, this one new style header, is in the Android SDK. +#if defined(ANDROID_NDK) || defined(__BORLANDC__) || defined(__QNXNTO__) +# include +# include +#else +# include +# include +#endif + +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) + // Microsoft Visual Studio, version 2005 and higher. Not WinCE. + /*int _snprintf_s( + char *buffer, + size_t sizeOfBuffer, + size_t count, + const char *format [, + argument] ... + );*/ + static inline int TIXML_SNPRINTF( char* buffer, size_t size, const char* format, ... ) + { + va_list va; + va_start( va, format ); + const int result = vsnprintf_s( buffer, size, _TRUNCATE, format, va ); + va_end( va ); + return result; + } + + static inline int TIXML_VSNPRINTF( char* buffer, size_t size, const char* format, va_list va ) + { + const int result = vsnprintf_s( buffer, size, _TRUNCATE, format, va ); + return result; + } + + #define TIXML_VSCPRINTF _vscprintf + #define TIXML_SSCANF sscanf_s +#elif defined _MSC_VER + // Microsoft Visual Studio 2003 and earlier or WinCE + #define TIXML_SNPRINTF _snprintf + #define TIXML_VSNPRINTF _vsnprintf + #define TIXML_SSCANF sscanf + #if (_MSC_VER < 1400 ) && (!defined WINCE) + // Microsoft Visual Studio 2003 and not WinCE. + #define TIXML_VSCPRINTF _vscprintf // VS2003's C runtime has this, but VC6 C runtime or WinCE SDK doesn't have. + #else + // Microsoft Visual Studio 2003 and earlier or WinCE. + static inline int TIXML_VSCPRINTF( const char* format, va_list va ) + { + int len = 512; + for (;;) { + len = len*2; + char* str = new char[len](); + const int required = _vsnprintf(str, len, format, va); + delete[] str; + if ( required != -1 ) { + TIXMLASSERT( required >= 0 ); + len = required; + break; + } + } + TIXMLASSERT( len >= 0 ); + return len; + } + #endif +#else + // GCC version 3 and higher + //#warning( "Using sn* functions." ) + #define TIXML_SNPRINTF snprintf + #define TIXML_VSNPRINTF vsnprintf + static inline int TIXML_VSCPRINTF( const char* format, va_list va ) + { + int len = vsnprintf( 0, 0, format, va ); + TIXMLASSERT( len >= 0 ); + return len; + } + #define TIXML_SSCANF sscanf +#endif + +#if defined(_WIN64) + #define TIXML_FSEEK _fseeki64 + #define TIXML_FTELL _ftelli64 +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__CYGWIN__) + #define TIXML_FSEEK fseeko + #define TIXML_FTELL ftello +#elif defined(__ANDROID__) + #if __ANDROID_API__ > 24 + #define TIXML_FSEEK fseeko64 + #define TIXML_FTELL ftello64 + #else + #define TIXML_FSEEK fseeko + #define TIXML_FTELL ftello + #endif +#else + #define TIXML_FSEEK fseek + #define TIXML_FTELL ftell +#endif + + +static const char LINE_FEED = static_cast(0x0a); // all line endings are normalized to LF +static const char LF = LINE_FEED; +static const char CARRIAGE_RETURN = static_cast(0x0d); // CR gets filtered out +static const char CR = CARRIAGE_RETURN; +static const char SINGLE_QUOTE = '\''; +static const char DOUBLE_QUOTE = '\"'; + +// Bunch of unicode info at: +// http://www.unicode.org/faq/utf_bom.html +// ef bb bf (Microsoft "lead bytes") - designates UTF-8 + +static const unsigned char TIXML_UTF_LEAD_0 = 0xefU; +static const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; +static const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + +namespace tinyxml2 +{ + +struct Entity { + const char* pattern; + int length; + char value; +}; + +static const int NUM_ENTITIES = 5; +static const Entity entities[NUM_ENTITIES] = { + { "quot", 4, DOUBLE_QUOTE }, + { "amp", 3, '&' }, + { "apos", 4, SINGLE_QUOTE }, + { "lt", 2, '<' }, + { "gt", 2, '>' } +}; + + +StrPair::~StrPair() +{ + Reset(); +} + + +void StrPair::TransferTo( StrPair* other ) +{ + if ( this == other ) { + return; + } + // This in effect implements the assignment operator by "moving" + // ownership (as in auto_ptr). + + TIXMLASSERT( other != 0 ); + TIXMLASSERT( other->_flags == 0 ); + TIXMLASSERT( other->_start == 0 ); + TIXMLASSERT( other->_end == 0 ); + + other->Reset(); + + other->_flags = _flags; + other->_start = _start; + other->_end = _end; + + _flags = 0; + _start = 0; + _end = 0; +} + + +void StrPair::Reset() +{ + if ( _flags & NEEDS_DELETE ) { + delete [] _start; + } + _flags = 0; + _start = 0; + _end = 0; +} + + +void StrPair::SetStr( const char* str, int flags ) +{ + TIXMLASSERT( str ); + Reset(); + size_t len = strlen( str ); + TIXMLASSERT( _start == 0 ); + _start = new char[ len+1 ]; + memcpy( _start, str, len+1 ); + _end = _start + len; + _flags = flags | NEEDS_DELETE; +} + + +char* StrPair::ParseText( char* p, const char* endTag, int strFlags, int* curLineNumPtr ) +{ + TIXMLASSERT( p ); + TIXMLASSERT( endTag && *endTag ); + TIXMLASSERT(curLineNumPtr); + + char* start = p; + const char endChar = *endTag; + size_t length = strlen( endTag ); + + // Inner loop of text parsing. + while ( *p ) { + if ( *p == endChar && strncmp( p, endTag, length ) == 0 ) { + Set( start, p, strFlags ); + return p + length; + } else if (*p == '\n') { + ++(*curLineNumPtr); + } + ++p; + TIXMLASSERT( p ); + } + return 0; +} + + +char* StrPair::ParseName( char* p ) +{ + if ( !p || !(*p) ) { + return 0; + } + if ( !XMLUtil::IsNameStartChar( (unsigned char) *p ) ) { + return 0; + } + + char* const start = p; + ++p; + while ( *p && XMLUtil::IsNameChar( (unsigned char) *p ) ) { + ++p; + } + + Set( start, p, 0 ); + return p; +} + + +void StrPair::CollapseWhitespace() +{ + // Adjusting _start would cause undefined behavior on delete[] + TIXMLASSERT( ( _flags & NEEDS_DELETE ) == 0 ); + // Trim leading space. + _start = XMLUtil::SkipWhiteSpace( _start, 0 ); + + if ( *_start ) { + const char* p = _start; // the read pointer + char* q = _start; // the write pointer + + while( *p ) { + if ( XMLUtil::IsWhiteSpace( *p )) { + p = XMLUtil::SkipWhiteSpace( p, 0 ); + if ( *p == 0 ) { + break; // don't write to q; this trims the trailing space. + } + *q = ' '; + ++q; + } + *q = *p; + ++q; + ++p; + } + *q = 0; + } +} + + +const char* StrPair::GetStr() +{ + TIXMLASSERT( _start ); + TIXMLASSERT( _end ); + if ( _flags & NEEDS_FLUSH ) { + *_end = 0; + _flags ^= NEEDS_FLUSH; + + if ( _flags ) { + const char* p = _start; // the read pointer + char* q = _start; // the write pointer + + while( p < _end ) { + if ( (_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == CR ) { + // CR-LF pair becomes LF + // CR alone becomes LF + // LF-CR becomes LF + if ( *(p+1) == LF ) { + p += 2; + } + else { + ++p; + } + *q = LF; + ++q; + } + else if ( (_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == LF ) { + if ( *(p+1) == CR ) { + p += 2; + } + else { + ++p; + } + *q = LF; + ++q; + } + else if ( (_flags & NEEDS_ENTITY_PROCESSING) && *p == '&' ) { + // Entities handled by tinyXML2: + // - special entities in the entity table [in/out] + // - numeric character reference [in] + // 中 or 中 + + if ( *(p+1) == '#' ) { + const int buflen = 10; + char buf[buflen] = { 0 }; + int len = 0; + const char* adjusted = const_cast( XMLUtil::GetCharacterRef( p, buf, &len ) ); + if ( adjusted == 0 ) { + *q = *p; + ++p; + ++q; + } + else { + TIXMLASSERT( 0 <= len && len <= buflen ); + TIXMLASSERT( q + len <= adjusted ); + p = adjusted; + memcpy( q, buf, len ); + q += len; + } + } + else { + bool entityFound = false; + for( int i = 0; i < NUM_ENTITIES; ++i ) { + const Entity& entity = entities[i]; + if ( strncmp( p + 1, entity.pattern, entity.length ) == 0 + && *( p + entity.length + 1 ) == ';' ) { + // Found an entity - convert. + *q = entity.value; + ++q; + p += entity.length + 2; + entityFound = true; + break; + } + } + if ( !entityFound ) { + // fixme: treat as error? + ++p; + ++q; + } + } + } + else { + *q = *p; + ++p; + ++q; + } + } + *q = 0; + } + // The loop below has plenty going on, and this + // is a less useful mode. Break it out. + if ( _flags & NEEDS_WHITESPACE_COLLAPSING ) { + CollapseWhitespace(); + } + _flags = (_flags & NEEDS_DELETE); + } + TIXMLASSERT( _start ); + return _start; +} + + + + +// --------- XMLUtil ----------- // + +const char* XMLUtil::writeBoolTrue = "true"; +const char* XMLUtil::writeBoolFalse = "false"; + +void XMLUtil::SetBoolSerialization(const char* writeTrue, const char* writeFalse) +{ + static const char* defTrue = "true"; + static const char* defFalse = "false"; + + writeBoolTrue = (writeTrue) ? writeTrue : defTrue; + writeBoolFalse = (writeFalse) ? writeFalse : defFalse; +} + + +const char* XMLUtil::ReadBOM( const char* p, bool* bom ) +{ + TIXMLASSERT( p ); + TIXMLASSERT( bom ); + *bom = false; + const unsigned char* pu = reinterpret_cast(p); + // Check for BOM: + if ( *(pu+0) == TIXML_UTF_LEAD_0 + && *(pu+1) == TIXML_UTF_LEAD_1 + && *(pu+2) == TIXML_UTF_LEAD_2 ) { + *bom = true; + p += 3; + } + TIXMLASSERT( p ); + return p; +} + + +void XMLUtil::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) +{ + const unsigned long BYTE_MASK = 0xBF; + const unsigned long BYTE_MARK = 0x80; + const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + if (input < 0x80) { + *length = 1; + } + else if ( input < 0x800 ) { + *length = 2; + } + else if ( input < 0x10000 ) { + *length = 3; + } + else if ( input < 0x200000 ) { + *length = 4; + } + else { + *length = 0; // This code won't convert this correctly anyway. + return; + } + + output += *length; + + // Scary scary fall throughs are annotated with carefully designed comments + // to suppress compiler warnings such as -Wimplicit-fallthrough in gcc + switch (*length) { + case 4: + --output; + *output = static_cast((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + //fall through + case 3: + --output; + *output = static_cast((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + //fall through + case 2: + --output; + *output = static_cast((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + //fall through + case 1: + --output; + *output = static_cast(input | FIRST_BYTE_MARK[*length]); + break; + default: + TIXMLASSERT( false ); + } +} + + +const char* XMLUtil::GetCharacterRef( const char* p, char* value, int* length ) +{ + // Presume an entity, and pull it out. + *length = 0; + + if ( *(p+1) == '#' && *(p+2) ) { + unsigned long ucs = 0; + TIXMLASSERT( sizeof( ucs ) >= 4 ); + ptrdiff_t delta = 0; + unsigned mult = 1; + static const char SEMICOLON = ';'; + + if ( *(p+2) == 'x' ) { + // Hexadecimal. + const char* q = p+3; + if ( !(*q) ) { + return 0; + } + + q = strchr( q, SEMICOLON ); + + if ( !q ) { + return 0; + } + TIXMLASSERT( *q == SEMICOLON ); + + delta = q-p; + --q; + + while ( *q != 'x' ) { + unsigned int digit = 0; + + if ( *q >= '0' && *q <= '9' ) { + digit = *q - '0'; + } + else if ( *q >= 'a' && *q <= 'f' ) { + digit = *q - 'a' + 10; + } + else if ( *q >= 'A' && *q <= 'F' ) { + digit = *q - 'A' + 10; + } + else { + return 0; + } + TIXMLASSERT( digit < 16 ); + TIXMLASSERT( digit == 0 || mult <= UINT_MAX / digit ); + const unsigned int digitScaled = mult * digit; + TIXMLASSERT( ucs <= ULONG_MAX - digitScaled ); + ucs += digitScaled; + TIXMLASSERT( mult <= UINT_MAX / 16 ); + mult *= 16; + --q; + } + } + else { + // Decimal. + const char* q = p+2; + if ( !(*q) ) { + return 0; + } + + q = strchr( q, SEMICOLON ); + + if ( !q ) { + return 0; + } + TIXMLASSERT( *q == SEMICOLON ); + + delta = q-p; + --q; + + while ( *q != '#' ) { + if ( *q >= '0' && *q <= '9' ) { + const unsigned int digit = *q - '0'; + TIXMLASSERT( digit < 10 ); + TIXMLASSERT( digit == 0 || mult <= UINT_MAX / digit ); + const unsigned int digitScaled = mult * digit; + TIXMLASSERT( ucs <= ULONG_MAX - digitScaled ); + ucs += digitScaled; + } + else { + return 0; + } + TIXMLASSERT( mult <= UINT_MAX / 10 ); + mult *= 10; + --q; + } + } + // convert the UCS to UTF-8 + ConvertUTF32ToUTF8( ucs, value, length ); + return p + delta + 1; + } + return p+1; +} + + +void XMLUtil::ToStr( int v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%d", v ); +} + + +void XMLUtil::ToStr( unsigned v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%u", v ); +} + + +void XMLUtil::ToStr( bool v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%s", v ? writeBoolTrue : writeBoolFalse); +} + +/* + ToStr() of a number is a very tricky topic. + https://github.com/leethomason/tinyxml2/issues/106 +*/ +void XMLUtil::ToStr( float v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%.8g", v ); +} + + +void XMLUtil::ToStr( double v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%.17g", v ); +} + + +void XMLUtil::ToStr( int64_t v, char* buffer, int bufferSize ) +{ + // horrible syntax trick to make the compiler happy about %lld + TIXML_SNPRINTF(buffer, bufferSize, "%lld", static_cast(v)); +} + +void XMLUtil::ToStr( uint64_t v, char* buffer, int bufferSize ) +{ + // horrible syntax trick to make the compiler happy about %llu + TIXML_SNPRINTF(buffer, bufferSize, "%llu", (long long)v); +} + +bool XMLUtil::ToInt(const char* str, int* value) +{ + if (IsPrefixHex(str)) { + unsigned v; + if (TIXML_SSCANF(str, "%x", &v) == 1) { + *value = static_cast(v); + return true; + } + } + else { + if (TIXML_SSCANF(str, "%d", value) == 1) { + return true; + } + } + return false; +} + +bool XMLUtil::ToUnsigned(const char* str, unsigned* value) +{ + if (TIXML_SSCANF(str, IsPrefixHex(str) ? "%x" : "%u", value) == 1) { + return true; + } + return false; +} + +bool XMLUtil::ToBool( const char* str, bool* value ) +{ + int ival = 0; + if ( ToInt( str, &ival )) { + *value = (ival==0) ? false : true; + return true; + } + static const char* TRUE_VALS[] = { "true", "True", "TRUE", 0 }; + static const char* FALSE_VALS[] = { "false", "False", "FALSE", 0 }; + + for (int i = 0; TRUE_VALS[i]; ++i) { + if (StringEqual(str, TRUE_VALS[i])) { + *value = true; + return true; + } + } + for (int i = 0; FALSE_VALS[i]; ++i) { + if (StringEqual(str, FALSE_VALS[i])) { + *value = false; + return true; + } + } + return false; +} + + +bool XMLUtil::ToFloat( const char* str, float* value ) +{ + if ( TIXML_SSCANF( str, "%f", value ) == 1 ) { + return true; + } + return false; +} + + +bool XMLUtil::ToDouble( const char* str, double* value ) +{ + if ( TIXML_SSCANF( str, "%lf", value ) == 1 ) { + return true; + } + return false; +} + + +bool XMLUtil::ToInt64(const char* str, int64_t* value) +{ + if (IsPrefixHex(str)) { + unsigned long long v = 0; // horrible syntax trick to make the compiler happy about %llx + if (TIXML_SSCANF(str, "%llx", &v) == 1) { + *value = static_cast(v); + return true; + } + } + else { + long long v = 0; // horrible syntax trick to make the compiler happy about %lld + if (TIXML_SSCANF(str, "%lld", &v) == 1) { + *value = static_cast(v); + return true; + } + } + return false; +} + + +bool XMLUtil::ToUnsigned64(const char* str, uint64_t* value) { + unsigned long long v = 0; // horrible syntax trick to make the compiler happy about %llu + if(TIXML_SSCANF(str, IsPrefixHex(str) ? "%llx" : "%llu", &v) == 1) { + *value = (uint64_t)v; + return true; + } + return false; +} + + +char* XMLDocument::Identify( char* p, XMLNode** node, bool first ) +{ + TIXMLASSERT( node ); + TIXMLASSERT( p ); + char* const start = p; + int const startLine = _parseCurLineNum; + p = XMLUtil::SkipWhiteSpace( p, &_parseCurLineNum ); + if( !*p ) { + *node = 0; + TIXMLASSERT( p ); + return p; + } + + // These strings define the matching patterns: + static const char* xmlHeader = { "( _commentPool ); + returnNode->_parseLineNum = _parseCurLineNum; + p += xmlHeaderLen; + } + else if ( XMLUtil::StringEqual( p, commentHeader, commentHeaderLen ) ) { + returnNode = CreateUnlinkedNode( _commentPool ); + returnNode->_parseLineNum = _parseCurLineNum; + p += commentHeaderLen; + } + else if ( XMLUtil::StringEqual( p, cdataHeader, cdataHeaderLen ) ) { + XMLText* text = CreateUnlinkedNode( _textPool ); + returnNode = text; + returnNode->_parseLineNum = _parseCurLineNum; + p += cdataHeaderLen; + text->SetCData( true ); + } + else if ( XMLUtil::StringEqual( p, dtdHeader, dtdHeaderLen ) ) { + returnNode = CreateUnlinkedNode( _commentPool ); + returnNode->_parseLineNum = _parseCurLineNum; + p += dtdHeaderLen; + } + else if ( XMLUtil::StringEqual( p, elementHeader, elementHeaderLen ) ) { + + // Preserve whitespace pedantically before closing tag, when it's immediately after opening tag + if (WhitespaceMode() == PEDANTIC_WHITESPACE && first && p != start && *(p + elementHeaderLen) == '/') { + returnNode = CreateUnlinkedNode(_textPool); + returnNode->_parseLineNum = startLine; + p = start; // Back it up, all the text counts. + _parseCurLineNum = startLine; + } + else { + returnNode = CreateUnlinkedNode(_elementPool); + returnNode->_parseLineNum = _parseCurLineNum; + p += elementHeaderLen; + } + } + else { + returnNode = CreateUnlinkedNode( _textPool ); + returnNode->_parseLineNum = _parseCurLineNum; // Report line of first non-whitespace character + p = start; // Back it up, all the text counts. + _parseCurLineNum = startLine; + } + + TIXMLASSERT( returnNode ); + TIXMLASSERT( p ); + *node = returnNode; + return p; +} + + +bool XMLDocument::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + if ( visitor->VisitEnter( *this ) ) { + for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() ) { + if ( !node->Accept( visitor ) ) { + break; + } + } + } + return visitor->VisitExit( *this ); +} + + +// --------- XMLNode ----------- // + +XMLNode::XMLNode( XMLDocument* doc ) : + _document( doc ), + _parent( 0 ), + _value(), + _parseLineNum( 0 ), + _firstChild( 0 ), _lastChild( 0 ), + _prev( 0 ), _next( 0 ), + _userData( 0 ), + _memPool( 0 ) +{ +} + + +XMLNode::~XMLNode() +{ + DeleteChildren(); + if ( _parent ) { + _parent->Unlink( this ); + } +} + +// ChildElementCount was originally suggested by msteiger on the sourceforge page for TinyXML and modified by KB1SPH for TinyXML-2. + +int XMLNode::ChildElementCount(const char *value) const { + int count = 0; + + const XMLElement *e = FirstChildElement(value); + + while (e) { + e = e->NextSiblingElement(value); + count++; + } + + return count; +} + +int XMLNode::ChildElementCount() const { + int count = 0; + + const XMLElement *e = FirstChildElement(); + + while (e) { + e = e->NextSiblingElement(); + count++; + } + + return count; +} + +const char* XMLNode::Value() const +{ + // Edge case: XMLDocuments don't have a Value. Return null. + if ( this->ToDocument() ) + return 0; + return _value.GetStr(); +} + +void XMLNode::SetValue( const char* str, bool staticMem ) +{ + if ( staticMem ) { + _value.SetInternedStr( str ); + } + else { + _value.SetStr( str ); + } +} + +XMLNode* XMLNode::DeepClone(XMLDocument* target) const +{ + XMLNode* clone = this->ShallowClone(target); + if (!clone) return 0; + + for (const XMLNode* child = this->FirstChild(); child; child = child->NextSibling()) { + XMLNode* childClone = child->DeepClone(target); + TIXMLASSERT(childClone); + clone->InsertEndChild(childClone); + } + return clone; +} + +void XMLNode::DeleteChildren() +{ + while( _firstChild ) { + TIXMLASSERT( _lastChild ); + DeleteChild( _firstChild ); + } + _firstChild = _lastChild = 0; +} + + +void XMLNode::Unlink( XMLNode* child ) +{ + TIXMLASSERT( child ); + TIXMLASSERT( child->_document == _document ); + TIXMLASSERT( child->_parent == this ); + if ( child == _firstChild ) { + _firstChild = _firstChild->_next; + } + if ( child == _lastChild ) { + _lastChild = _lastChild->_prev; + } + + if ( child->_prev ) { + child->_prev->_next = child->_next; + } + if ( child->_next ) { + child->_next->_prev = child->_prev; + } + child->_next = 0; + child->_prev = 0; + child->_parent = 0; +} + + +void XMLNode::DeleteChild( XMLNode* node ) +{ + TIXMLASSERT( node ); + TIXMLASSERT( node->_document == _document ); + TIXMLASSERT( node->_parent == this ); + Unlink( node ); + TIXMLASSERT(node->_prev == 0); + TIXMLASSERT(node->_next == 0); + TIXMLASSERT(node->_parent == 0); + DeleteNode( node ); +} + + +XMLNode* XMLNode::InsertEndChild( XMLNode* addThis ) +{ + TIXMLASSERT( addThis ); + if ( addThis->_document != _document ) { + TIXMLASSERT( false ); + return 0; + } + InsertChildPreamble( addThis ); + + if ( _lastChild ) { + TIXMLASSERT( _firstChild ); + TIXMLASSERT( _lastChild->_next == 0 ); + _lastChild->_next = addThis; + addThis->_prev = _lastChild; + _lastChild = addThis; + + addThis->_next = 0; + } + else { + TIXMLASSERT( _firstChild == 0 ); + _firstChild = _lastChild = addThis; + + addThis->_prev = 0; + addThis->_next = 0; + } + addThis->_parent = this; + return addThis; +} + + +XMLNode* XMLNode::InsertFirstChild( XMLNode* addThis ) +{ + TIXMLASSERT( addThis ); + if ( addThis->_document != _document ) { + TIXMLASSERT( false ); + return 0; + } + InsertChildPreamble( addThis ); + + if ( _firstChild ) { + TIXMLASSERT( _lastChild ); + TIXMLASSERT( _firstChild->_prev == 0 ); + + _firstChild->_prev = addThis; + addThis->_next = _firstChild; + _firstChild = addThis; + + addThis->_prev = 0; + } + else { + TIXMLASSERT( _lastChild == 0 ); + _firstChild = _lastChild = addThis; + + addThis->_prev = 0; + addThis->_next = 0; + } + addThis->_parent = this; + return addThis; +} + + +XMLNode* XMLNode::InsertAfterChild( XMLNode* afterThis, XMLNode* addThis ) +{ + TIXMLASSERT( addThis ); + if ( addThis->_document != _document ) { + TIXMLASSERT( false ); + return 0; + } + + TIXMLASSERT( afterThis ); + + if ( afterThis->_parent != this ) { + TIXMLASSERT( false ); + return 0; + } + if ( afterThis == addThis ) { + // Current state: BeforeThis -> AddThis -> OneAfterAddThis + // Now AddThis must disappear from it's location and then + // reappear between BeforeThis and OneAfterAddThis. + // So just leave it where it is. + return addThis; + } + + if ( afterThis->_next == 0 ) { + // The last node or the only node. + return InsertEndChild( addThis ); + } + InsertChildPreamble( addThis ); + addThis->_prev = afterThis; + addThis->_next = afterThis->_next; + afterThis->_next->_prev = addThis; + afterThis->_next = addThis; + addThis->_parent = this; + return addThis; +} + + + + +const XMLElement* XMLNode::FirstChildElement( const char* name ) const +{ + for( const XMLNode* node = _firstChild; node; node = node->_next ) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { + return element; + } + } + return 0; +} + + +const XMLElement* XMLNode::LastChildElement( const char* name ) const +{ + for( const XMLNode* node = _lastChild; node; node = node->_prev ) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { + return element; + } + } + return 0; +} + + +const XMLElement* XMLNode::NextSiblingElement( const char* name ) const +{ + for( const XMLNode* node = _next; node; node = node->_next ) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { + return element; + } + } + return 0; +} + + +const XMLElement* XMLNode::PreviousSiblingElement( const char* name ) const +{ + for( const XMLNode* node = _prev; node; node = node->_prev ) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { + return element; + } + } + return 0; +} + + +char* XMLNode::ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ) +{ + // This is a recursive method, but thinking about it "at the current level" + // it is a pretty simple flat list: + // + // + // + // With a special case: + // + // + // + // + // Where the closing element (/foo) *must* be the next thing after the opening + // element, and the names must match. BUT the tricky bit is that the closing + // element will be read by the child. + // + // 'endTag' is the end tag for this node, it is returned by a call to a child. + // 'parentEnd' is the end tag for the parent, which is filled in and returned. + + XMLDocument::DepthTracker tracker(_document); + if (_document->Error()) + return 0; + + bool first = true; + while( p && *p ) { + XMLNode* node = 0; + + p = _document->Identify( p, &node, first ); + TIXMLASSERT( p ); + if ( node == 0 ) { + break; + } + first = false; + + const int initialLineNum = node->_parseLineNum; + + StrPair endTag; + p = node->ParseDeep( p, &endTag, curLineNumPtr ); + if ( !p ) { + _document->DeleteNode( node ); + if ( !_document->Error() ) { + _document->SetError( XML_ERROR_PARSING, initialLineNum, 0); + } + break; + } + + const XMLDeclaration* const decl = node->ToDeclaration(); + if ( decl ) { + // Declarations are only allowed at document level + // + // Multiple declarations are allowed but all declarations + // must occur before anything else. + // + // Optimized due to a security test case. If the first node is + // a declaration, and the last node is a declaration, then only + // declarations have so far been added. + bool wellLocated = false; + + if (ToDocument()) { + if (FirstChild()) { + wellLocated = + FirstChild() && + FirstChild()->ToDeclaration() && + LastChild() && + LastChild()->ToDeclaration(); + } + else { + wellLocated = true; + } + } + if ( !wellLocated ) { + _document->SetError( XML_ERROR_PARSING_DECLARATION, initialLineNum, "XMLDeclaration value=%s", decl->Value()); + _document->DeleteNode( node ); + break; + } + } + + XMLElement* ele = node->ToElement(); + if ( ele ) { + // We read the end tag. Return it to the parent. + if ( ele->ClosingType() == XMLElement::CLOSING ) { + if ( parentEndTag ) { + ele->_value.TransferTo( parentEndTag ); + } + node->_memPool->SetTracked(); // created and then immediately deleted. + DeleteNode( node ); + return p; + } + + // Handle an end tag returned to this level. + // And handle a bunch of annoying errors. + bool mismatch = false; + if ( endTag.Empty() ) { + if ( ele->ClosingType() == XMLElement::OPEN ) { + mismatch = true; + } + } + else { + if ( ele->ClosingType() != XMLElement::OPEN ) { + mismatch = true; + } + else if ( !XMLUtil::StringEqual( endTag.GetStr(), ele->Name() ) ) { + mismatch = true; + } + } + if ( mismatch ) { + _document->SetError( XML_ERROR_MISMATCHED_ELEMENT, initialLineNum, "XMLElement name=%s", ele->Name()); + _document->DeleteNode( node ); + break; + } + } + InsertEndChild( node ); + } + return 0; +} + +/*static*/ void XMLNode::DeleteNode( XMLNode* node ) +{ + if ( node == 0 ) { + return; + } + TIXMLASSERT(node->_document); + if (!node->ToDocument()) { + node->_document->MarkInUse(node); + } + + MemPool* pool = node->_memPool; + node->~XMLNode(); + pool->Free( node ); +} + +void XMLNode::InsertChildPreamble( XMLNode* insertThis ) const +{ + TIXMLASSERT( insertThis ); + TIXMLASSERT( insertThis->_document == _document ); + + if (insertThis->_parent) { + insertThis->_parent->Unlink( insertThis ); + } + else { + insertThis->_document->MarkInUse(insertThis); + insertThis->_memPool->SetTracked(); + } +} + +const XMLElement* XMLNode::ToElementWithName( const char* name ) const +{ + const XMLElement* element = this->ToElement(); + if ( element == 0 ) { + return 0; + } + if ( name == 0 ) { + return element; + } + if ( XMLUtil::StringEqual( element->Name(), name ) ) { + return element; + } + return 0; +} + +// --------- XMLText ---------- // +char* XMLText::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) +{ + if ( this->CData() ) { + p = _value.ParseText( p, "]]>", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr ); + if ( !p ) { + _document->SetError( XML_ERROR_PARSING_CDATA, _parseLineNum, 0 ); + } + return p; + } + else { + int flags = _document->ProcessEntities() ? StrPair::TEXT_ELEMENT : StrPair::TEXT_ELEMENT_LEAVE_ENTITIES; + if ( _document->WhitespaceMode() == COLLAPSE_WHITESPACE ) { + flags |= StrPair::NEEDS_WHITESPACE_COLLAPSING; + } + + p = _value.ParseText( p, "<", flags, curLineNumPtr ); + if ( p && *p ) { + return p-1; + } + if ( !p ) { + _document->SetError( XML_ERROR_PARSING_TEXT, _parseLineNum, 0 ); + } + } + return 0; +} + + +XMLNode* XMLText::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLText* text = doc->NewText( Value() ); // fixme: this will always allocate memory. Intern? + text->SetCData( this->CData() ); + return text; +} + + +bool XMLText::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLText* text = compare->ToText(); + return ( text && XMLUtil::StringEqual( text->Value(), Value() ) ); +} + + +bool XMLText::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + return visitor->Visit( *this ); +} + + +// --------- XMLComment ---------- // + +XMLComment::XMLComment( XMLDocument* doc ) : XMLNode( doc ) +{ +} + + +XMLComment::~XMLComment() +{ +} + + +char* XMLComment::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) +{ + // Comment parses as text. + p = _value.ParseText( p, "-->", StrPair::COMMENT, curLineNumPtr ); + if ( p == 0 ) { + _document->SetError( XML_ERROR_PARSING_COMMENT, _parseLineNum, 0 ); + } + return p; +} + + +XMLNode* XMLComment::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLComment* comment = doc->NewComment( Value() ); // fixme: this will always allocate memory. Intern? + return comment; +} + + +bool XMLComment::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLComment* comment = compare->ToComment(); + return ( comment && XMLUtil::StringEqual( comment->Value(), Value() )); +} + + +bool XMLComment::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + return visitor->Visit( *this ); +} + + +// --------- XMLDeclaration ---------- // + +XMLDeclaration::XMLDeclaration( XMLDocument* doc ) : XMLNode( doc ) +{ +} + + +XMLDeclaration::~XMLDeclaration() +{ + //printf( "~XMLDeclaration\n" ); +} + + +char* XMLDeclaration::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) +{ + // Declaration parses as text. + p = _value.ParseText( p, "?>", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr ); + if ( p == 0 ) { + _document->SetError( XML_ERROR_PARSING_DECLARATION, _parseLineNum, 0 ); + } + return p; +} + + +XMLNode* XMLDeclaration::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLDeclaration* dec = doc->NewDeclaration( Value() ); // fixme: this will always allocate memory. Intern? + return dec; +} + + +bool XMLDeclaration::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLDeclaration* declaration = compare->ToDeclaration(); + return ( declaration && XMLUtil::StringEqual( declaration->Value(), Value() )); +} + + + +bool XMLDeclaration::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + return visitor->Visit( *this ); +} + +// --------- XMLUnknown ---------- // + +XMLUnknown::XMLUnknown( XMLDocument* doc ) : XMLNode( doc ) +{ +} + + +XMLUnknown::~XMLUnknown() +{ +} + + +char* XMLUnknown::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) +{ + // Unknown parses as text. + p = _value.ParseText( p, ">", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr ); + if ( !p ) { + _document->SetError( XML_ERROR_PARSING_UNKNOWN, _parseLineNum, 0 ); + } + return p; +} + + +XMLNode* XMLUnknown::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLUnknown* text = doc->NewUnknown( Value() ); // fixme: this will always allocate memory. Intern? + return text; +} + + +bool XMLUnknown::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLUnknown* unknown = compare->ToUnknown(); + return ( unknown && XMLUtil::StringEqual( unknown->Value(), Value() )); +} + + +bool XMLUnknown::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + return visitor->Visit( *this ); +} + +// --------- XMLAttribute ---------- // + +const char* XMLAttribute::Name() const +{ + return _name.GetStr(); +} + +const char* XMLAttribute::Value() const +{ + return _value.GetStr(); +} + +char* XMLAttribute::ParseDeep( char* p, bool processEntities, int* curLineNumPtr ) +{ + // Parse using the name rules: bug fix, was using ParseText before + p = _name.ParseName( p ); + if ( !p || !*p ) { + return 0; + } + + // Skip white space before = + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); + if ( *p != '=' ) { + return 0; + } + + ++p; // move up to opening quote + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); + if ( *p != '\"' && *p != '\'' ) { + return 0; + } + + const char endTag[2] = { *p, 0 }; + ++p; // move past opening quote + + p = _value.ParseText( p, endTag, processEntities ? StrPair::ATTRIBUTE_VALUE : StrPair::ATTRIBUTE_VALUE_LEAVE_ENTITIES, curLineNumPtr ); + return p; +} + + +void XMLAttribute::SetName( const char* n ) +{ + _name.SetStr( n ); +} + + +XMLError XMLAttribute::QueryIntValue( int* value ) const +{ + if ( XMLUtil::ToInt( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryUnsignedValue( unsigned int* value ) const +{ + if ( XMLUtil::ToUnsigned( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryInt64Value(int64_t* value) const +{ + if (XMLUtil::ToInt64(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryUnsigned64Value(uint64_t* value) const +{ + if(XMLUtil::ToUnsigned64(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryBoolValue( bool* value ) const +{ + if ( XMLUtil::ToBool( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryFloatValue( float* value ) const +{ + if ( XMLUtil::ToFloat( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryDoubleValue( double* value ) const +{ + if ( XMLUtil::ToDouble( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +void XMLAttribute::SetAttribute( const char* v ) +{ + _value.SetStr( v ); +} + + +void XMLAttribute::SetAttribute( int v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + + +void XMLAttribute::SetAttribute( unsigned v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + + +void XMLAttribute::SetAttribute(int64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); +} + +void XMLAttribute::SetAttribute(uint64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); +} + + +void XMLAttribute::SetAttribute( bool v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + +void XMLAttribute::SetAttribute( double v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + +void XMLAttribute::SetAttribute( float v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + + +// --------- XMLElement ---------- // +XMLElement::XMLElement( XMLDocument* doc ) : XMLNode( doc ), + _closingType( OPEN ), + _rootAttribute( 0 ) +{ +} + + +XMLElement::~XMLElement() +{ + while( _rootAttribute ) { + XMLAttribute* next = _rootAttribute->_next; + DeleteAttribute( _rootAttribute ); + _rootAttribute = next; + } +} + + +const XMLAttribute* XMLElement::FindAttribute( const char* name ) const +{ + for( XMLAttribute* a = _rootAttribute; a; a = a->_next ) { + if ( XMLUtil::StringEqual( a->Name(), name ) ) { + return a; + } + } + return 0; +} + + +const char* XMLElement::Attribute( const char* name, const char* value ) const +{ + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return 0; + } + if ( !value || XMLUtil::StringEqual( a->Value(), value )) { + return a->Value(); + } + return 0; +} + +int XMLElement::IntAttribute(const char* name, int defaultValue) const +{ + int i = defaultValue; + QueryIntAttribute(name, &i); + return i; +} + +unsigned XMLElement::UnsignedAttribute(const char* name, unsigned defaultValue) const +{ + unsigned i = defaultValue; + QueryUnsignedAttribute(name, &i); + return i; +} + +int64_t XMLElement::Int64Attribute(const char* name, int64_t defaultValue) const +{ + int64_t i = defaultValue; + QueryInt64Attribute(name, &i); + return i; +} + +uint64_t XMLElement::Unsigned64Attribute(const char* name, uint64_t defaultValue) const +{ + uint64_t i = defaultValue; + QueryUnsigned64Attribute(name, &i); + return i; +} + +bool XMLElement::BoolAttribute(const char* name, bool defaultValue) const +{ + bool b = defaultValue; + QueryBoolAttribute(name, &b); + return b; +} + +double XMLElement::DoubleAttribute(const char* name, double defaultValue) const +{ + double d = defaultValue; + QueryDoubleAttribute(name, &d); + return d; +} + +float XMLElement::FloatAttribute(const char* name, float defaultValue) const +{ + float f = defaultValue; + QueryFloatAttribute(name, &f); + return f; +} + +const char* XMLElement::GetText() const +{ + /* skip comment node */ + const XMLNode* node = FirstChild(); + while (node) { + if (node->ToComment()) { + node = node->NextSibling(); + continue; + } + break; + } + + if ( node && node->ToText() ) { + return node->Value(); + } + return 0; +} + + +void XMLElement::SetText( const char* inText ) +{ + if ( FirstChild() && FirstChild()->ToText() ) + FirstChild()->SetValue( inText ); + else { + XMLText* theText = GetDocument()->NewText( inText ); + InsertFirstChild( theText ); + } +} + + +void XMLElement::SetText( int v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( unsigned v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText(int64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); +} + +void XMLElement::SetText(uint64_t v) { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); +} + + +void XMLElement::SetText( bool v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( float v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( double v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +XMLError XMLElement::QueryIntText( int* ival ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToInt( t, ival ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryUnsignedText( unsigned* uval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToUnsigned( t, uval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryInt64Text(int64_t* ival) const +{ + if (FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if (XMLUtil::ToInt64(t, ival)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryUnsigned64Text(uint64_t* uval) const +{ + if(FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if(XMLUtil::ToUnsigned64(t, uval)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryBoolText( bool* bval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToBool( t, bval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryDoubleText( double* dval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToDouble( t, dval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryFloatText( float* fval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToFloat( t, fval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + +int XMLElement::IntText(int defaultValue) const +{ + int i = defaultValue; + QueryIntText(&i); + return i; +} + +unsigned XMLElement::UnsignedText(unsigned defaultValue) const +{ + unsigned i = defaultValue; + QueryUnsignedText(&i); + return i; +} + +int64_t XMLElement::Int64Text(int64_t defaultValue) const +{ + int64_t i = defaultValue; + QueryInt64Text(&i); + return i; +} + +uint64_t XMLElement::Unsigned64Text(uint64_t defaultValue) const +{ + uint64_t i = defaultValue; + QueryUnsigned64Text(&i); + return i; +} + +bool XMLElement::BoolText(bool defaultValue) const +{ + bool b = defaultValue; + QueryBoolText(&b); + return b; +} + +double XMLElement::DoubleText(double defaultValue) const +{ + double d = defaultValue; + QueryDoubleText(&d); + return d; +} + +float XMLElement::FloatText(float defaultValue) const +{ + float f = defaultValue; + QueryFloatText(&f); + return f; +} + + +XMLAttribute* XMLElement::FindOrCreateAttribute( const char* name ) +{ + XMLAttribute* last = 0; + XMLAttribute* attrib = 0; + for( attrib = _rootAttribute; + attrib; + last = attrib, attrib = attrib->_next ) { + if ( XMLUtil::StringEqual( attrib->Name(), name ) ) { + break; + } + } + if ( !attrib ) { + attrib = CreateAttribute(); + TIXMLASSERT( attrib ); + if ( last ) { + TIXMLASSERT( last->_next == 0 ); + last->_next = attrib; + } + else { + TIXMLASSERT( _rootAttribute == 0 ); + _rootAttribute = attrib; + } + attrib->SetName( name ); + } + return attrib; +} + + +void XMLElement::DeleteAttribute( const char* name ) +{ + XMLAttribute* prev = 0; + for( XMLAttribute* a=_rootAttribute; a; a=a->_next ) { + if ( XMLUtil::StringEqual( name, a->Name() ) ) { + if ( prev ) { + prev->_next = a->_next; + } + else { + _rootAttribute = a->_next; + } + DeleteAttribute( a ); + break; + } + prev = a; + } +} + + +char* XMLElement::ParseAttributes( char* p, int* curLineNumPtr ) +{ + XMLAttribute* prevAttribute = 0; + + // Read the attributes. + while( p ) { + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); + if ( !(*p) ) { + _document->SetError( XML_ERROR_PARSING_ELEMENT, _parseLineNum, "XMLElement name=%s", Name() ); + return 0; + } + + // attribute. + if (XMLUtil::IsNameStartChar( (unsigned char) *p ) ) { + XMLAttribute* attrib = CreateAttribute(); + TIXMLASSERT( attrib ); + attrib->_parseLineNum = _document->_parseCurLineNum; + + const int attrLineNum = attrib->_parseLineNum; + + p = attrib->ParseDeep( p, _document->ProcessEntities(), curLineNumPtr ); + if ( !p || Attribute( attrib->Name() ) ) { + DeleteAttribute( attrib ); + _document->SetError( XML_ERROR_PARSING_ATTRIBUTE, attrLineNum, "XMLElement name=%s", Name() ); + return 0; + } + // There is a minor bug here: if the attribute in the source xml + // document is duplicated, it will not be detected and the + // attribute will be doubly added. However, tracking the 'prevAttribute' + // avoids re-scanning the attribute list. Preferring performance for + // now, may reconsider in the future. + if ( prevAttribute ) { + TIXMLASSERT( prevAttribute->_next == 0 ); + prevAttribute->_next = attrib; + } + else { + TIXMLASSERT( _rootAttribute == 0 ); + _rootAttribute = attrib; + } + prevAttribute = attrib; + } + // end of the tag + else if ( *p == '>' ) { + ++p; + break; + } + // end of the tag + else if ( *p == '/' && *(p+1) == '>' ) { + _closingType = CLOSED; + return p+2; // done; sealed element. + } + else { + _document->SetError( XML_ERROR_PARSING_ELEMENT, _parseLineNum, 0 ); + return 0; + } + } + return p; +} + +void XMLElement::DeleteAttribute( XMLAttribute* attribute ) +{ + if ( attribute == 0 ) { + return; + } + MemPool* pool = attribute->_memPool; + attribute->~XMLAttribute(); + pool->Free( attribute ); +} + +XMLAttribute* XMLElement::CreateAttribute() +{ + TIXMLASSERT( sizeof( XMLAttribute ) == _document->_attributePool.ItemSize() ); + XMLAttribute* attrib = new (_document->_attributePool.Alloc() ) XMLAttribute(); + TIXMLASSERT( attrib ); + attrib->_memPool = &_document->_attributePool; + attrib->_memPool->SetTracked(); + return attrib; +} + + +XMLElement* XMLElement::InsertNewChildElement(const char* name) +{ + XMLElement* node = _document->NewElement(name); + return InsertEndChild(node) ? node : 0; +} + +XMLComment* XMLElement::InsertNewComment(const char* comment) +{ + XMLComment* node = _document->NewComment(comment); + return InsertEndChild(node) ? node : 0; +} + +XMLText* XMLElement::InsertNewText(const char* text) +{ + XMLText* node = _document->NewText(text); + return InsertEndChild(node) ? node : 0; +} + +XMLDeclaration* XMLElement::InsertNewDeclaration(const char* text) +{ + XMLDeclaration* node = _document->NewDeclaration(text); + return InsertEndChild(node) ? node : 0; +} + +XMLUnknown* XMLElement::InsertNewUnknown(const char* text) +{ + XMLUnknown* node = _document->NewUnknown(text); + return InsertEndChild(node) ? node : 0; +} + + + +// +// +// foobar +// +char* XMLElement::ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ) +{ + // Read the element name. + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); + + // The closing element is the form. It is + // parsed just like a regular element then deleted from + // the DOM. + if ( *p == '/' ) { + _closingType = CLOSING; + ++p; + } + + p = _value.ParseName( p ); + if ( _value.Empty() ) { + return 0; + } + + p = ParseAttributes( p, curLineNumPtr ); + if ( !p || !*p || _closingType != OPEN ) { + return p; + } + + p = XMLNode::ParseDeep( p, parentEndTag, curLineNumPtr ); + return p; +} + + + +XMLNode* XMLElement::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLElement* element = doc->NewElement( Value() ); // fixme: this will always allocate memory. Intern? + for( const XMLAttribute* a=FirstAttribute(); a; a=a->Next() ) { + element->SetAttribute( a->Name(), a->Value() ); // fixme: this will always allocate memory. Intern? + } + return element; +} + + +bool XMLElement::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLElement* other = compare->ToElement(); + if ( other && XMLUtil::StringEqual( other->Name(), Name() )) { + + const XMLAttribute* a=FirstAttribute(); + const XMLAttribute* b=other->FirstAttribute(); + + while ( a && b ) { + if ( !XMLUtil::StringEqual( a->Value(), b->Value() ) ) { + return false; + } + a = a->Next(); + b = b->Next(); + } + if ( a || b ) { + // different count + return false; + } + return true; + } + return false; +} + + +bool XMLElement::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + if ( visitor->VisitEnter( *this, _rootAttribute ) ) { + for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() ) { + if ( !node->Accept( visitor ) ) { + break; + } + } + } + return visitor->VisitExit( *this ); +} + + +// --------- XMLDocument ----------- // + +// Warning: List must match 'enum XMLError' +const char* XMLDocument::_errorNames[XML_ERROR_COUNT] = { + "XML_SUCCESS", + "XML_NO_ATTRIBUTE", + "XML_WRONG_ATTRIBUTE_TYPE", + "XML_ERROR_FILE_NOT_FOUND", + "XML_ERROR_FILE_COULD_NOT_BE_OPENED", + "XML_ERROR_FILE_READ_ERROR", + "XML_ERROR_PARSING_ELEMENT", + "XML_ERROR_PARSING_ATTRIBUTE", + "XML_ERROR_PARSING_TEXT", + "XML_ERROR_PARSING_CDATA", + "XML_ERROR_PARSING_COMMENT", + "XML_ERROR_PARSING_DECLARATION", + "XML_ERROR_PARSING_UNKNOWN", + "XML_ERROR_EMPTY_DOCUMENT", + "XML_ERROR_MISMATCHED_ELEMENT", + "XML_ERROR_PARSING", + "XML_CAN_NOT_CONVERT_TEXT", + "XML_NO_TEXT_NODE", + "XML_ELEMENT_DEPTH_EXCEEDED" +}; + + +XMLDocument::XMLDocument( bool processEntities, Whitespace whitespaceMode ) : + XMLNode( 0 ), + _writeBOM( false ), + _processEntities( processEntities ), + _errorID(XML_SUCCESS), + _whitespaceMode( whitespaceMode ), + _errorStr(), + _errorLineNum( 0 ), + _charBuffer( 0 ), + _parseCurLineNum( 0 ), + _parsingDepth(0), + _unlinked(), + _elementPool(), + _attributePool(), + _textPool(), + _commentPool() +{ + // avoid VC++ C4355 warning about 'this' in initializer list (C4355 is off by default in VS2012+) + _document = this; +} + + +XMLDocument::~XMLDocument() +{ + Clear(); +} + + +void XMLDocument::MarkInUse(const XMLNode* const node) +{ + TIXMLASSERT(node); + TIXMLASSERT(node->_parent == 0); + + for (int i = 0; i < _unlinked.Size(); ++i) { + if (node == _unlinked[i]) { + _unlinked.SwapRemove(i); + break; + } + } +} + +void XMLDocument::Clear() +{ + DeleteChildren(); + while( _unlinked.Size()) { + DeleteNode(_unlinked[0]); // Will remove from _unlinked as part of delete. + } + +#ifdef TINYXML2_DEBUG + const bool hadError = Error(); +#endif + ClearError(); + + delete [] _charBuffer; + _charBuffer = 0; + _parsingDepth = 0; + +#if 0 + _textPool.Trace( "text" ); + _elementPool.Trace( "element" ); + _commentPool.Trace( "comment" ); + _attributePool.Trace( "attribute" ); +#endif + +#ifdef TINYXML2_DEBUG + if ( !hadError ) { + TIXMLASSERT( _elementPool.CurrentAllocs() == _elementPool.Untracked() ); + TIXMLASSERT( _attributePool.CurrentAllocs() == _attributePool.Untracked() ); + TIXMLASSERT( _textPool.CurrentAllocs() == _textPool.Untracked() ); + TIXMLASSERT( _commentPool.CurrentAllocs() == _commentPool.Untracked() ); + } +#endif +} + + +void XMLDocument::DeepCopy(XMLDocument* target) const +{ + TIXMLASSERT(target); + if (target == this) { + return; // technically success - a no-op. + } + + target->Clear(); + for (const XMLNode* node = this->FirstChild(); node; node = node->NextSibling()) { + target->InsertEndChild(node->DeepClone(target)); + } +} + +XMLElement* XMLDocument::NewElement( const char* name ) +{ + XMLElement* ele = CreateUnlinkedNode( _elementPool ); + ele->SetName( name ); + return ele; +} + + +XMLComment* XMLDocument::NewComment( const char* str ) +{ + XMLComment* comment = CreateUnlinkedNode( _commentPool ); + comment->SetValue( str ); + return comment; +} + + +XMLText* XMLDocument::NewText( const char* str ) +{ + XMLText* text = CreateUnlinkedNode( _textPool ); + text->SetValue( str ); + return text; +} + + +XMLDeclaration* XMLDocument::NewDeclaration( const char* str ) +{ + XMLDeclaration* dec = CreateUnlinkedNode( _commentPool ); + dec->SetValue( str ? str : "xml version=\"1.0\" encoding=\"UTF-8\"" ); + return dec; +} + + +XMLUnknown* XMLDocument::NewUnknown( const char* str ) +{ + XMLUnknown* unk = CreateUnlinkedNode( _commentPool ); + unk->SetValue( str ); + return unk; +} + +static FILE* callfopen( const char* filepath, const char* mode ) +{ + TIXMLASSERT( filepath ); + TIXMLASSERT( mode ); +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) + FILE* fp = 0; + const errno_t err = fopen_s( &fp, filepath, mode ); + if ( err ) { + return 0; + } +#else + FILE* fp = fopen( filepath, mode ); +#endif + return fp; +} + +void XMLDocument::DeleteNode( XMLNode* node ) { + TIXMLASSERT( node ); + TIXMLASSERT(node->_document == this ); + if (node->_parent) { + node->_parent->DeleteChild( node ); + } + else { + // Isn't in the tree. + // Use the parent delete. + // Also, we need to mark it tracked: we 'know' + // it was never used. + node->_memPool->SetTracked(); + // Call the static XMLNode version: + XMLNode::DeleteNode(node); + } +} + + +XMLError XMLDocument::LoadFile( const char* filename ) +{ + if ( !filename ) { + TIXMLASSERT( false ); + SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, "filename=" ); + return _errorID; + } + + Clear(); + FILE* fp = callfopen( filename, "rb" ); + if ( !fp ) { + SetError( XML_ERROR_FILE_NOT_FOUND, 0, "filename=%s", filename ); + return _errorID; + } + LoadFile( fp ); + fclose( fp ); + return _errorID; +} + +XMLError XMLDocument::LoadFile( FILE* fp ) +{ + Clear(); + + TIXML_FSEEK( fp, 0, SEEK_SET ); + if ( fgetc( fp ) == EOF && ferror( fp ) != 0 ) { + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + + TIXML_FSEEK( fp, 0, SEEK_END ); + + unsigned long long filelength; + { + const long long fileLengthSigned = TIXML_FTELL( fp ); + TIXML_FSEEK( fp, 0, SEEK_SET ); + if ( fileLengthSigned == -1L ) { + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + TIXMLASSERT( fileLengthSigned >= 0 ); + filelength = static_cast(fileLengthSigned); + } + + const size_t maxSizeT = static_cast(-1); + // We'll do the comparison as an unsigned long long, because that's guaranteed to be at + // least 8 bytes, even on a 32-bit platform. + if ( filelength >= static_cast(maxSizeT) ) { + // Cannot handle files which won't fit in buffer together with null terminator + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + + if ( filelength == 0 ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return _errorID; + } + + const size_t size = static_cast(filelength); + TIXMLASSERT( _charBuffer == 0 ); + _charBuffer = new char[size+1]; + const size_t read = fread( _charBuffer, 1, size, fp ); + if ( read != size ) { + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + + _charBuffer[size] = 0; + + Parse(); + return _errorID; +} + + +XMLError XMLDocument::SaveFile( const char* filename, bool compact ) +{ + if ( !filename ) { + TIXMLASSERT( false ); + SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, "filename=" ); + return _errorID; + } + + FILE* fp = callfopen( filename, "w" ); + if ( !fp ) { + SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, "filename=%s", filename ); + return _errorID; + } + SaveFile(fp, compact); + fclose( fp ); + return _errorID; +} + + +XMLError XMLDocument::SaveFile( FILE* fp, bool compact ) +{ + // Clear any error from the last save, otherwise it will get reported + // for *this* call. + ClearError(); + XMLPrinter stream( fp, compact ); + Print( &stream ); + return _errorID; +} + + +XMLError XMLDocument::Parse( const char* xml, size_t nBytes ) +{ + Clear(); + + if ( nBytes == 0 || !xml || !*xml ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return _errorID; + } + if ( nBytes == static_cast(-1) ) { + nBytes = strlen( xml ); + } + TIXMLASSERT( _charBuffer == 0 ); + _charBuffer = new char[ nBytes+1 ]; + memcpy( _charBuffer, xml, nBytes ); + _charBuffer[nBytes] = 0; + + Parse(); + if ( Error() ) { + // clean up now essentially dangling memory. + // and the parse fail can put objects in the + // pools that are dead and inaccessible. + DeleteChildren(); + _elementPool.Clear(); + _attributePool.Clear(); + _textPool.Clear(); + _commentPool.Clear(); + } + return _errorID; +} + + +void XMLDocument::Print( XMLPrinter* streamer ) const +{ + if ( streamer ) { + Accept( streamer ); + } + else { + XMLPrinter stdoutStreamer( stdout ); + Accept( &stdoutStreamer ); + } +} + + +void XMLDocument::ClearError() { + _errorID = XML_SUCCESS; + _errorLineNum = 0; + _errorStr.Reset(); +} + + +void XMLDocument::SetError( XMLError error, int lineNum, const char* format, ... ) +{ + TIXMLASSERT( error >= 0 && error < XML_ERROR_COUNT ); + _errorID = error; + _errorLineNum = lineNum; + _errorStr.Reset(); + + const size_t BUFFER_SIZE = 1000; + char* buffer = new char[BUFFER_SIZE]; + + TIXMLASSERT(sizeof(error) <= sizeof(int)); + TIXML_SNPRINTF(buffer, BUFFER_SIZE, "Error=%s ErrorID=%d (0x%x) Line number=%d", ErrorIDToName(error), int(error), int(error), lineNum); + + if (format) { + size_t len = strlen(buffer); + TIXML_SNPRINTF(buffer + len, BUFFER_SIZE - len, ": "); + len = strlen(buffer); + + va_list va; + va_start(va, format); + TIXML_VSNPRINTF(buffer + len, BUFFER_SIZE - len, format, va); + va_end(va); + } + _errorStr.SetStr(buffer); + delete[] buffer; +} + + +/*static*/ const char* XMLDocument::ErrorIDToName(XMLError errorID) +{ + TIXMLASSERT( errorID >= 0 && errorID < XML_ERROR_COUNT ); + const char* errorName = _errorNames[errorID]; + TIXMLASSERT( errorName && errorName[0] ); + return errorName; +} + +const char* XMLDocument::ErrorStr() const +{ + return _errorStr.Empty() ? "" : _errorStr.GetStr(); +} + + +void XMLDocument::PrintError() const +{ + printf("%s\n", ErrorStr()); +} + +const char* XMLDocument::ErrorName() const +{ + return ErrorIDToName(_errorID); +} + +void XMLDocument::Parse() +{ + TIXMLASSERT( NoChildren() ); // Clear() must have been called previously + TIXMLASSERT( _charBuffer ); + _parseCurLineNum = 1; + _parseLineNum = 1; + char* p = _charBuffer; + p = XMLUtil::SkipWhiteSpace( p, &_parseCurLineNum ); + p = const_cast( XMLUtil::ReadBOM( p, &_writeBOM ) ); + if ( !*p ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return; + } + ParseDeep(p, 0, &_parseCurLineNum ); +} + +void XMLDocument::PushDepth() +{ + _parsingDepth++; + if (_parsingDepth == TINYXML2_MAX_ELEMENT_DEPTH) { + SetError(XML_ELEMENT_DEPTH_EXCEEDED, _parseCurLineNum, "Element nesting is too deep." ); + } +} + +void XMLDocument::PopDepth() +{ + TIXMLASSERT(_parsingDepth > 0); + --_parsingDepth; +} + +XMLPrinter::XMLPrinter( FILE* file, bool compact, int depth ) : + _elementJustOpened( false ), + _stack(), + _firstElement( true ), + _fp( file ), + _depth( depth ), + _textDepth( -1 ), + _processEntities( true ), + _compactMode( compact ), + _buffer() +{ + for( int i=0; i(entityValue); + TIXMLASSERT( flagIndex < ENTITY_RANGE ); + _entityFlag[flagIndex] = true; + } + _restrictedEntityFlag[static_cast('&')] = true; + _restrictedEntityFlag[static_cast('<')] = true; + _restrictedEntityFlag[static_cast('>')] = true; // not required, but consistency is nice + _buffer.Push( 0 ); +} + + +void XMLPrinter::Print( const char* format, ... ) +{ + va_list va; + va_start( va, format ); + + if ( _fp ) { + vfprintf( _fp, format, va ); + } + else { + const int len = TIXML_VSCPRINTF( format, va ); + // Close out and re-start the va-args + va_end( va ); + TIXMLASSERT( len >= 0 ); + va_start( va, format ); + TIXMLASSERT( _buffer.Size() > 0 && _buffer[_buffer.Size() - 1] == 0 ); + char* p = _buffer.PushArr( len ) - 1; // back up over the null terminator. + TIXML_VSNPRINTF( p, len+1, format, va ); + } + va_end( va ); +} + + +void XMLPrinter::Write( const char* data, size_t size ) +{ + if ( _fp ) { + fwrite ( data , sizeof(char), size, _fp); + } + else { + char* p = _buffer.PushArr( static_cast(size) ) - 1; // back up over the null terminator. + memcpy( p, data, size ); + p[size] = 0; + } +} + + +void XMLPrinter::Putc( char ch ) +{ + if ( _fp ) { + fputc ( ch, _fp); + } + else { + char* p = _buffer.PushArr( sizeof(char) ) - 1; // back up over the null terminator. + p[0] = ch; + p[1] = 0; + } +} + + +void XMLPrinter::PrintSpace( int depth ) +{ + for( int i=0; i 0 && *q < ENTITY_RANGE ) { + // Check for entities. If one is found, flush + // the stream up until the entity, write the + // entity, and keep looking. + if ( flag[static_cast(*q)] ) { + while ( p < q ) { + const size_t delta = q - p; + const int toPrint = ( INT_MAX < delta ) ? INT_MAX : static_cast(delta); + Write( p, toPrint ); + p += toPrint; + } + bool entityPatternPrinted = false; + for( int i=0; i(delta); + Write( p, toPrint ); + } + } + else { + Write( p ); + } +} + + +void XMLPrinter::PushHeader( bool writeBOM, bool writeDec ) +{ + if ( writeBOM ) { + static const unsigned char bom[] = { TIXML_UTF_LEAD_0, TIXML_UTF_LEAD_1, TIXML_UTF_LEAD_2, 0 }; + Write( reinterpret_cast< const char* >( bom ) ); + } + if ( writeDec ) { + PushDeclaration( "xml version=\"1.0\"" ); + } +} + +void XMLPrinter::PrepareForNewNode( bool compactMode ) +{ + SealElementIfJustOpened(); + + if ( compactMode ) { + return; + } + + if ( _firstElement ) { + PrintSpace (_depth); + } else if ( _textDepth < 0) { + Putc( '\n' ); + PrintSpace( _depth ); + } + + _firstElement = false; +} + +void XMLPrinter::OpenElement( const char* name, bool compactMode ) +{ + PrepareForNewNode( compactMode ); + _stack.Push( name ); + + Write ( "<" ); + Write ( name ); + + _elementJustOpened = true; + ++_depth; +} + + +void XMLPrinter::PushAttribute( const char* name, const char* value ) +{ + TIXMLASSERT( _elementJustOpened ); + Putc ( ' ' ); + Write( name ); + Write( "=\"" ); + PrintString( value, false ); + Putc ( '\"' ); +} + + +void XMLPrinter::PushAttribute( const char* name, int v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::PushAttribute( const char* name, unsigned v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::PushAttribute(const char* name, int64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + PushAttribute(name, buf); +} + + +void XMLPrinter::PushAttribute(const char* name, uint64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + PushAttribute(name, buf); +} + + +void XMLPrinter::PushAttribute( const char* name, bool v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::PushAttribute( const char* name, double v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::CloseElement( bool compactMode ) +{ + --_depth; + const char* name = _stack.Pop(); + + if ( _elementJustOpened ) { + Write( "/>" ); + } + else { + if ( _textDepth < 0 && !compactMode) { + Putc( '\n' ); + PrintSpace( _depth ); + } + Write ( "" ); + } + + if ( _textDepth == _depth ) { + _textDepth = -1; + } + if ( _depth == 0 && !compactMode) { + Putc( '\n' ); + } + _elementJustOpened = false; +} + + +void XMLPrinter::SealElementIfJustOpened() +{ + if ( !_elementJustOpened ) { + return; + } + _elementJustOpened = false; + Putc( '>' ); +} + + +void XMLPrinter::PushText( const char* text, bool cdata ) +{ + _textDepth = _depth-1; + + SealElementIfJustOpened(); + if ( cdata ) { + Write( "" ); + } + else { + PrintString( text, true ); + } +} + + +void XMLPrinter::PushText( int64_t value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( uint64_t value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(value, buf, BUF_SIZE); + PushText(buf, false); +} + + +void XMLPrinter::PushText( int value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( unsigned value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( bool value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( float value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( double value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushComment( const char* comment ) +{ + PrepareForNewNode( _compactMode ); + + Write( "" ); +} + + +void XMLPrinter::PushDeclaration( const char* value ) +{ + PrepareForNewNode( _compactMode ); + + Write( "" ); +} + + +void XMLPrinter::PushUnknown( const char* value ) +{ + PrepareForNewNode( _compactMode ); + + Write( "' ); +} + + +bool XMLPrinter::VisitEnter( const XMLDocument& doc ) +{ + _processEntities = doc.ProcessEntities(); + if ( doc.HasBOM() ) { + PushHeader( true, false ); + } + return true; +} + + +bool XMLPrinter::VisitEnter( const XMLElement& element, const XMLAttribute* attribute ) +{ + const XMLElement* parentElem = 0; + if ( element.Parent() ) { + parentElem = element.Parent()->ToElement(); + } + const bool compactMode = parentElem ? CompactMode( *parentElem ) : _compactMode; + OpenElement( element.Name(), compactMode ); + while ( attribute ) { + PushAttribute( attribute->Name(), attribute->Value() ); + attribute = attribute->Next(); + } + return true; +} + + +bool XMLPrinter::VisitExit( const XMLElement& element ) +{ + CloseElement( CompactMode(element) ); + return true; +} + + +bool XMLPrinter::Visit( const XMLText& text ) +{ + PushText( text.Value(), text.CData() ); + return true; +} + + +bool XMLPrinter::Visit( const XMLComment& comment ) +{ + PushComment( comment.Value() ); + return true; +} + +bool XMLPrinter::Visit( const XMLDeclaration& declaration ) +{ + PushDeclaration( declaration.Value() ); + return true; +} + + +bool XMLPrinter::Visit( const XMLUnknown& unknown ) +{ + PushUnknown( unknown.Value() ); + return true; +} + +} // namespace tinyxml2 diff --git a/src/atom/type/tinyxml2.h b/src/atom/type/tinyxml2.h new file mode 100755 index 00000000..e3aa3f6b --- /dev/null +++ b/src/atom/type/tinyxml2.h @@ -0,0 +1,2387 @@ +/* +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#ifndef TINYXML2_INCLUDED +#define TINYXML2_INCLUDED + +#if defined(ANDROID_NDK) || defined(__BORLANDC__) || defined(__QNXNTO__) +# include +# include +# include +# include +# include +# if defined(__PS3__) +# include +# endif +#else +# include +# include +# include +# include +# include +#endif +#include + +/* + TODO: intern strings instead of allocation. +*/ +/* + gcc: + g++ -Wall -DTINYXML2_DEBUG tinyxml2.cpp xmltest.cpp -o gccxmltest.exe + + Formatting, Artistic Style: + AStyle.exe --style=1tbs --indent-switches --break-closing-brackets --indent-preprocessor tinyxml2.cpp tinyxml2.h +*/ + +#if defined( _DEBUG ) || defined (__DEBUG__) +# ifndef TINYXML2_DEBUG +# define TINYXML2_DEBUG +# endif +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4251) +#endif + +#ifdef _MSC_VER +# ifdef TINYXML2_EXPORT +# define TINYXML2_LIB __declspec(dllexport) +# elif defined(TINYXML2_IMPORT) +# define TINYXML2_LIB __declspec(dllimport) +# else +# define TINYXML2_LIB +# endif +#elif __GNUC__ >= 4 +# define TINYXML2_LIB __attribute__((visibility("default"))) +#else +# define TINYXML2_LIB +#endif + + +#if !defined(TIXMLASSERT) +#if defined(TINYXML2_DEBUG) +# if defined(_MSC_VER) +# // "(void)0," is for suppressing C4127 warning in "assert(false)", "assert(true)" and the like +# define TIXMLASSERT( x ) do { if ( !((void)0,(x))) { __debugbreak(); } } while(false) +# elif defined (ANDROID_NDK) +# include +# define TIXMLASSERT( x ) do { if ( !(x)) { __android_log_assert( "assert", "grinliz", "ASSERT in '%s' at %d.", __FILE__, __LINE__ ); } } while(false) +# else +# include +# define TIXMLASSERT assert +# endif +#else +# define TIXMLASSERT( x ) do {} while(false) +#endif +#endif + +/* Versioning, past 1.0.14: + http://semver.org/ +*/ +static const int TIXML2_MAJOR_VERSION = 9; +static const int TIXML2_MINOR_VERSION = 0; +static const int TIXML2_PATCH_VERSION = 0; + +#define TINYXML2_MAJOR_VERSION 9 +#define TINYXML2_MINOR_VERSION 0 +#define TINYXML2_PATCH_VERSION 0 + +// A fixed element depth limit is problematic. There needs to be a +// limit to avoid a stack overflow. However, that limit varies per +// system, and the capacity of the stack. On the other hand, it's a trivial +// attack that can result from ill, malicious, or even correctly formed XML, +// so there needs to be a limit in place. +static const int TINYXML2_MAX_ELEMENT_DEPTH = 500; + +namespace tinyxml2 +{ +class XMLDocument; +class XMLElement; +class XMLAttribute; +class XMLComment; +class XMLText; +class XMLDeclaration; +class XMLUnknown; +class XMLPrinter; + +/* + A class that wraps strings. Normally stores the start and end + pointers into the XML file itself, and will apply normalization + and entity translation if actually read. Can also store (and memory + manage) a traditional char[] + + Isn't clear why TINYXML2_LIB is needed; but seems to fix #719 +*/ +class TINYXML2_LIB StrPair +{ +public: + enum Mode { + NEEDS_ENTITY_PROCESSING = 0x01, + NEEDS_NEWLINE_NORMALIZATION = 0x02, + NEEDS_WHITESPACE_COLLAPSING = 0x04, + + TEXT_ELEMENT = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, + TEXT_ELEMENT_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, + ATTRIBUTE_NAME = 0, + ATTRIBUTE_VALUE = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, + ATTRIBUTE_VALUE_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, + COMMENT = NEEDS_NEWLINE_NORMALIZATION + }; + + StrPair() : _flags( 0 ), _start( 0 ), _end( 0 ) {} + ~StrPair(); + + void Set( char* start, char* end, int flags ) { + TIXMLASSERT( start ); + TIXMLASSERT( end ); + Reset(); + _start = start; + _end = end; + _flags = flags | NEEDS_FLUSH; + } + + const char* GetStr(); + + bool Empty() const { + return _start == _end; + } + + void SetInternedStr( const char* str ) { + Reset(); + _start = const_cast(str); + } + + void SetStr( const char* str, int flags=0 ); + + char* ParseText( char* in, const char* endTag, int strFlags, int* curLineNumPtr ); + char* ParseName( char* in ); + + void TransferTo( StrPair* other ); + void Reset(); + +private: + void CollapseWhitespace(); + + enum { + NEEDS_FLUSH = 0x100, + NEEDS_DELETE = 0x200 + }; + + int _flags; + char* _start; + char* _end; + + StrPair( const StrPair& other ); // not supported + void operator=( const StrPair& other ); // not supported, use TransferTo() +}; + + +/* + A dynamic array of Plain Old Data. Doesn't support constructors, etc. + Has a small initial memory pool, so that low or no usage will not + cause a call to new/delete +*/ +template +class DynArray +{ +public: + DynArray() : + _mem( _pool ), + _allocated( INITIAL_SIZE ), + _size( 0 ) + { + } + + ~DynArray() { + if ( _mem != _pool ) { + delete [] _mem; + } + } + + void Clear() { + _size = 0; + } + + void Push( T t ) { + TIXMLASSERT( _size < INT_MAX ); + EnsureCapacity( _size+1 ); + _mem[_size] = t; + ++_size; + } + + T* PushArr( int count ) { + TIXMLASSERT( count >= 0 ); + TIXMLASSERT( _size <= INT_MAX - count ); + EnsureCapacity( _size+count ); + T* ret = &_mem[_size]; + _size += count; + return ret; + } + + T Pop() { + TIXMLASSERT( _size > 0 ); + --_size; + return _mem[_size]; + } + + void PopArr( int count ) { + TIXMLASSERT( _size >= count ); + _size -= count; + } + + bool Empty() const { + return _size == 0; + } + + T& operator[](int i) { + TIXMLASSERT( i>= 0 && i < _size ); + return _mem[i]; + } + + const T& operator[](int i) const { + TIXMLASSERT( i>= 0 && i < _size ); + return _mem[i]; + } + + const T& PeekTop() const { + TIXMLASSERT( _size > 0 ); + return _mem[ _size - 1]; + } + + int Size() const { + TIXMLASSERT( _size >= 0 ); + return _size; + } + + int Capacity() const { + TIXMLASSERT( _allocated >= INITIAL_SIZE ); + return _allocated; + } + + void SwapRemove(int i) { + TIXMLASSERT(i >= 0 && i < _size); + TIXMLASSERT(_size > 0); + _mem[i] = _mem[_size - 1]; + --_size; + } + + const T* Mem() const { + TIXMLASSERT( _mem ); + return _mem; + } + + T* Mem() { + TIXMLASSERT( _mem ); + return _mem; + } + +private: + DynArray( const DynArray& ); // not supported + void operator=( const DynArray& ); // not supported + + void EnsureCapacity( int cap ) { + TIXMLASSERT( cap > 0 ); + if ( cap > _allocated ) { + TIXMLASSERT( cap <= INT_MAX / 2 ); + const int newAllocated = cap * 2; + T* newMem = new T[static_cast(newAllocated)]; + TIXMLASSERT( newAllocated >= _size ); + memcpy( newMem, _mem, sizeof(T)*static_cast(_size) ); // warning: not using constructors, only works for PODs + if ( _mem != _pool ) { + delete [] _mem; + } + _mem = newMem; + _allocated = newAllocated; + } + } + + T* _mem; + T _pool[static_cast(INITIAL_SIZE)]; + int _allocated; // objects allocated + int _size; // number objects in use +}; + + +/* + Parent virtual class of a pool for fast allocation + and deallocation of objects. +*/ +class MemPool +{ +public: + MemPool() {} + virtual ~MemPool() {} + + virtual int ItemSize() const = 0; + virtual void* Alloc() = 0; + virtual void Free( void* ) = 0; + virtual void SetTracked() = 0; +}; + + +/* + Template child class to create pools of the correct type. +*/ +template< int ITEM_SIZE > +class MemPoolT : public MemPool +{ +public: + MemPoolT() : _blockPtrs(), _root(0), _currentAllocs(0), _nAllocs(0), _maxAllocs(0), _nUntracked(0) {} + ~MemPoolT() { + MemPoolT< ITEM_SIZE >::Clear(); + } + + void Clear() { + // Delete the blocks. + while( !_blockPtrs.Empty()) { + Block* lastBlock = _blockPtrs.Pop(); + delete lastBlock; + } + _root = 0; + _currentAllocs = 0; + _nAllocs = 0; + _maxAllocs = 0; + _nUntracked = 0; + } + + virtual int ItemSize() const override{ + return ITEM_SIZE; + } + int CurrentAllocs() const { + return _currentAllocs; + } + + virtual void* Alloc() override{ + if ( !_root ) { + // Need a new block. + Block* block = new Block; + _blockPtrs.Push( block ); + + Item* blockItems = block->items; + for( int i = 0; i < ITEMS_PER_BLOCK - 1; ++i ) { + blockItems[i].next = &(blockItems[i + 1]); + } + blockItems[ITEMS_PER_BLOCK - 1].next = 0; + _root = blockItems; + } + Item* const result = _root; + TIXMLASSERT( result != 0 ); + _root = _root->next; + + ++_currentAllocs; + if ( _currentAllocs > _maxAllocs ) { + _maxAllocs = _currentAllocs; + } + ++_nAllocs; + ++_nUntracked; + return result; + } + + virtual void Free( void* mem ) override { + if ( !mem ) { + return; + } + --_currentAllocs; + Item* item = static_cast( mem ); +#ifdef TINYXML2_DEBUG + memset( item, 0xfe, sizeof( *item ) ); +#endif + item->next = _root; + _root = item; + } + void Trace( const char* name ) { + printf( "Mempool %s watermark=%d [%dk] current=%d size=%d nAlloc=%d blocks=%d\n", + name, _maxAllocs, _maxAllocs * ITEM_SIZE / 1024, _currentAllocs, + ITEM_SIZE, _nAllocs, _blockPtrs.Size() ); + } + + void SetTracked() override { + --_nUntracked; + } + + int Untracked() const { + return _nUntracked; + } + + // This number is perf sensitive. 4k seems like a good tradeoff on my machine. + // The test file is large, 170k. + // Release: VS2010 gcc(no opt) + // 1k: 4000 + // 2k: 4000 + // 4k: 3900 21000 + // 16k: 5200 + // 32k: 4300 + // 64k: 4000 21000 + // Declared public because some compilers do not accept to use ITEMS_PER_BLOCK + // in private part if ITEMS_PER_BLOCK is private + enum { ITEMS_PER_BLOCK = (4 * 1024) / ITEM_SIZE }; + +private: + MemPoolT( const MemPoolT& ); // not supported + void operator=( const MemPoolT& ); // not supported + + union Item { + Item* next; + char itemData[static_cast(ITEM_SIZE)]; + }; + struct Block { + Item items[ITEMS_PER_BLOCK]; + }; + DynArray< Block*, 10 > _blockPtrs; + Item* _root; + + int _currentAllocs; + int _nAllocs; + int _maxAllocs; + int _nUntracked; +}; + + + +/** + Implements the interface to the "Visitor pattern" (see the Accept() method.) + If you call the Accept() method, it requires being passed a XMLVisitor + class to handle callbacks. For nodes that contain other nodes (Document, Element) + you will get called with a VisitEnter/VisitExit pair. Nodes that are always leafs + are simply called with Visit(). + + If you return 'true' from a Visit method, recursive parsing will continue. If you return + false, no children of this node or its siblings will be visited. + + All flavors of Visit methods have a default implementation that returns 'true' (continue + visiting). You need to only override methods that are interesting to you. + + Generally Accept() is called on the XMLDocument, although all nodes support visiting. + + You should never change the document from a callback. + + @sa XMLNode::Accept() +*/ +class TINYXML2_LIB XMLVisitor +{ +public: + virtual ~XMLVisitor() {} + + /// Visit a document. + virtual bool VisitEnter( const XMLDocument& /*doc*/ ) { + return true; + } + /// Visit a document. + virtual bool VisitExit( const XMLDocument& /*doc*/ ) { + return true; + } + + /// Visit an element. + virtual bool VisitEnter( const XMLElement& /*element*/, const XMLAttribute* /*firstAttribute*/ ) { + return true; + } + /// Visit an element. + virtual bool VisitExit( const XMLElement& /*element*/ ) { + return true; + } + + /// Visit a declaration. + virtual bool Visit( const XMLDeclaration& /*declaration*/ ) { + return true; + } + /// Visit a text node. + virtual bool Visit( const XMLText& /*text*/ ) { + return true; + } + /// Visit a comment node. + virtual bool Visit( const XMLComment& /*comment*/ ) { + return true; + } + /// Visit an unknown node. + virtual bool Visit( const XMLUnknown& /*unknown*/ ) { + return true; + } +}; + +// WARNING: must match XMLDocument::_errorNames[] +enum XMLError { + XML_SUCCESS = 0, + XML_NO_ATTRIBUTE, + XML_WRONG_ATTRIBUTE_TYPE, + XML_ERROR_FILE_NOT_FOUND, + XML_ERROR_FILE_COULD_NOT_BE_OPENED, + XML_ERROR_FILE_READ_ERROR, + XML_ERROR_PARSING_ELEMENT, + XML_ERROR_PARSING_ATTRIBUTE, + XML_ERROR_PARSING_TEXT, + XML_ERROR_PARSING_CDATA, + XML_ERROR_PARSING_COMMENT, + XML_ERROR_PARSING_DECLARATION, + XML_ERROR_PARSING_UNKNOWN, + XML_ERROR_EMPTY_DOCUMENT, + XML_ERROR_MISMATCHED_ELEMENT, + XML_ERROR_PARSING, + XML_CAN_NOT_CONVERT_TEXT, + XML_NO_TEXT_NODE, + XML_ELEMENT_DEPTH_EXCEEDED, + + XML_ERROR_COUNT +}; + + +/* + Utility functionality. +*/ +class TINYXML2_LIB XMLUtil +{ +public: + static const char* SkipWhiteSpace( const char* p, int* curLineNumPtr ) { + TIXMLASSERT( p ); + + while( IsWhiteSpace(*p) ) { + if (curLineNumPtr && *p == '\n') { + ++(*curLineNumPtr); + } + ++p; + } + TIXMLASSERT( p ); + return p; + } + static char* SkipWhiteSpace( char* const p, int* curLineNumPtr ) { + return const_cast( SkipWhiteSpace( const_cast(p), curLineNumPtr ) ); + } + + // Anything in the high order range of UTF-8 is assumed to not be whitespace. This isn't + // correct, but simple, and usually works. + static bool IsWhiteSpace( char p ) { + return !IsUTF8Continuation(p) && isspace( static_cast(p) ); + } + + inline static bool IsNameStartChar( unsigned char ch ) { + if ( ch >= 128 ) { + // This is a heuristic guess in attempt to not implement Unicode-aware isalpha() + return true; + } + if ( isalpha( ch ) ) { + return true; + } + return ch == ':' || ch == '_'; + } + + inline static bool IsNameChar( unsigned char ch ) { + return IsNameStartChar( ch ) + || isdigit( ch ) + || ch == '.' + || ch == '-'; + } + + inline static bool IsPrefixHex( const char* p) { + p = SkipWhiteSpace(p, 0); + return p && *p == '0' && ( *(p + 1) == 'x' || *(p + 1) == 'X'); + } + + inline static bool StringEqual( const char* p, const char* q, int nChar=INT_MAX ) { + if ( p == q ) { + return true; + } + TIXMLASSERT( p ); + TIXMLASSERT( q ); + TIXMLASSERT( nChar >= 0 ); + return strncmp( p, q, static_cast(nChar) ) == 0; + } + + inline static bool IsUTF8Continuation( const char p ) { + return ( p & 0x80 ) != 0; + } + + static const char* ReadBOM( const char* p, bool* hasBOM ); + // p is the starting location, + // the UTF-8 value of the entity will be placed in value, and length filled in. + static const char* GetCharacterRef( const char* p, char* value, int* length ); + static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ); + + // converts primitive types to strings + static void ToStr( int v, char* buffer, int bufferSize ); + static void ToStr( unsigned v, char* buffer, int bufferSize ); + static void ToStr( bool v, char* buffer, int bufferSize ); + static void ToStr( float v, char* buffer, int bufferSize ); + static void ToStr( double v, char* buffer, int bufferSize ); + static void ToStr(int64_t v, char* buffer, int bufferSize); + static void ToStr(uint64_t v, char* buffer, int bufferSize); + + // converts strings to primitive types + static bool ToInt( const char* str, int* value ); + static bool ToUnsigned( const char* str, unsigned* value ); + static bool ToBool( const char* str, bool* value ); + static bool ToFloat( const char* str, float* value ); + static bool ToDouble( const char* str, double* value ); + static bool ToInt64(const char* str, int64_t* value); + static bool ToUnsigned64(const char* str, uint64_t* value); + // Changes what is serialized for a boolean value. + // Default to "true" and "false". Shouldn't be changed + // unless you have a special testing or compatibility need. + // Be careful: static, global, & not thread safe. + // Be sure to set static const memory as parameters. + static void SetBoolSerialization(const char* writeTrue, const char* writeFalse); + +private: + static const char* writeBoolTrue; + static const char* writeBoolFalse; +}; + + +/** XMLNode is a base class for every object that is in the + XML Document Object Model (DOM), except XMLAttributes. + Nodes have siblings, a parent, and children which can + be navigated. A node is always in a XMLDocument. + The type of a XMLNode can be queried, and it can + be cast to its more defined type. + + A XMLDocument allocates memory for all its Nodes. + When the XMLDocument gets deleted, all its Nodes + will also be deleted. + + @verbatim + A Document can contain: Element (container or leaf) + Comment (leaf) + Unknown (leaf) + Declaration( leaf ) + + An Element can contain: Element (container or leaf) + Text (leaf) + Attributes (not on tree) + Comment (leaf) + Unknown (leaf) + + @endverbatim +*/ +class TINYXML2_LIB XMLNode +{ + friend class XMLDocument; + friend class XMLElement; +public: + + /// Get the XMLDocument that owns this XMLNode. + const XMLDocument* GetDocument() const { + TIXMLASSERT( _document ); + return _document; + } + /// Get the XMLDocument that owns this XMLNode. + XMLDocument* GetDocument() { + TIXMLASSERT( _document ); + return _document; + } + + /// Safely cast to an Element, or null. + virtual XMLElement* ToElement() { + return 0; + } + /// Safely cast to Text, or null. + virtual XMLText* ToText() { + return 0; + } + /// Safely cast to a Comment, or null. + virtual XMLComment* ToComment() { + return 0; + } + /// Safely cast to a Document, or null. + virtual XMLDocument* ToDocument() { + return 0; + } + /// Safely cast to a Declaration, or null. + virtual XMLDeclaration* ToDeclaration() { + return 0; + } + /// Safely cast to an Unknown, or null. + virtual XMLUnknown* ToUnknown() { + return 0; + } + + virtual const XMLElement* ToElement() const { + return 0; + } + virtual const XMLText* ToText() const { + return 0; + } + virtual const XMLComment* ToComment() const { + return 0; + } + virtual const XMLDocument* ToDocument() const { + return 0; + } + virtual const XMLDeclaration* ToDeclaration() const { + return 0; + } + virtual const XMLUnknown* ToUnknown() const { + return 0; + } + + // ChildElementCount was originally suggested by msteiger on the sourceforge page for TinyXML and modified by KB1SPH for TinyXML-2. + + int ChildElementCount(const char *value) const; + + int ChildElementCount() const; + + /** The meaning of 'value' changes for the specific type. + @verbatim + Document: empty (NULL is returned, not an empty string) + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + */ + const char* Value() const; + + /** Set the Value of an XML node. + @sa Value() + */ + void SetValue( const char* val, bool staticMem=false ); + + /// Gets the line number the node is in, if the document was parsed from a file. + int GetLineNum() const { return _parseLineNum; } + + /// Get the parent of this node on the DOM. + const XMLNode* Parent() const { + return _parent; + } + + XMLNode* Parent() { + return _parent; + } + + /// Returns true if this node has no children. + bool NoChildren() const { + return !_firstChild; + } + + /// Get the first child node, or null if none exists. + const XMLNode* FirstChild() const { + return _firstChild; + } + + XMLNode* FirstChild() { + return _firstChild; + } + + /** Get the first child element, or optionally the first child + element with the specified name. + */ + const XMLElement* FirstChildElement( const char* name = 0 ) const; + + XMLElement* FirstChildElement( const char* name = 0 ) { + return const_cast(const_cast(this)->FirstChildElement( name )); + } + + /// Get the last child node, or null if none exists. + const XMLNode* LastChild() const { + return _lastChild; + } + + XMLNode* LastChild() { + return _lastChild; + } + + /** Get the last child element or optionally the last child + element with the specified name. + */ + const XMLElement* LastChildElement( const char* name = 0 ) const; + + XMLElement* LastChildElement( const char* name = 0 ) { + return const_cast(const_cast(this)->LastChildElement(name) ); + } + + /// Get the previous (left) sibling node of this node. + const XMLNode* PreviousSibling() const { + return _prev; + } + + XMLNode* PreviousSibling() { + return _prev; + } + + /// Get the previous (left) sibling element of this node, with an optionally supplied name. + const XMLElement* PreviousSiblingElement( const char* name = 0 ) const ; + + XMLElement* PreviousSiblingElement( const char* name = 0 ) { + return const_cast(const_cast(this)->PreviousSiblingElement( name ) ); + } + + /// Get the next (right) sibling node of this node. + const XMLNode* NextSibling() const { + return _next; + } + + XMLNode* NextSibling() { + return _next; + } + + /// Get the next (right) sibling element of this node, with an optionally supplied name. + const XMLElement* NextSiblingElement( const char* name = 0 ) const; + + XMLElement* NextSiblingElement( const char* name = 0 ) { + return const_cast(const_cast(this)->NextSiblingElement( name ) ); + } + + /** + Add a child node as the last (right) child. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the node does not + belong to the same document. + */ + XMLNode* InsertEndChild( XMLNode* addThis ); + + XMLNode* LinkEndChild( XMLNode* addThis ) { + return InsertEndChild( addThis ); + } + /** + Add a child node as the first (left) child. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the node does not + belong to the same document. + */ + XMLNode* InsertFirstChild( XMLNode* addThis ); + /** + Add a node after the specified child node. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the afterThis node + is not a child of this node, or if the node does not + belong to the same document. + */ + XMLNode* InsertAfterChild( XMLNode* afterThis, XMLNode* addThis ); + + /** + Delete all the children of this node. + */ + void DeleteChildren(); + + /** + Delete a child of this node. + */ + void DeleteChild( XMLNode* node ); + + /** + Make a copy of this node, but not its children. + You may pass in a Document pointer that will be + the owner of the new Node. If the 'document' is + null, then the node returned will be allocated + from the current Document. (this->GetDocument()) + + Note: if called on a XMLDocument, this will return null. + */ + virtual XMLNode* ShallowClone( XMLDocument* document ) const = 0; + + /** + Make a copy of this node and all its children. + + If the 'target' is null, then the nodes will + be allocated in the current document. If 'target' + is specified, the memory will be allocated is the + specified XMLDocument. + + NOTE: This is probably not the correct tool to + copy a document, since XMLDocuments can have multiple + top level XMLNodes. You probably want to use + XMLDocument::DeepCopy() + */ + XMLNode* DeepClone( XMLDocument* target ) const; + + /** + Test if 2 nodes are the same, but don't test children. + The 2 nodes do not need to be in the same Document. + + Note: if called on a XMLDocument, this will return false. + */ + virtual bool ShallowEqual( const XMLNode* compare ) const = 0; + + /** Accept a hierarchical visit of the nodes in the TinyXML-2 DOM. Every node in the + XML tree will be conditionally visited and the host will be called back + via the XMLVisitor interface. + + This is essentially a SAX interface for TinyXML-2. (Note however it doesn't re-parse + the XML for the callbacks, so the performance of TinyXML-2 is unchanged by using this + interface versus any other.) + + The interface has been based on ideas from: + + - http://www.saxproject.org/ + - http://c2.com/cgi/wiki?HierarchicalVisitorPattern + + Which are both good references for "visiting". + + An example of using Accept(): + @verbatim + XMLPrinter printer; + tinyxmlDoc.Accept( &printer ); + const char* xmlcstr = printer.CStr(); + @endverbatim + */ + virtual bool Accept( XMLVisitor* visitor ) const = 0; + + /** + Set user data into the XMLNode. TinyXML-2 in + no way processes or interprets user data. + It is initially 0. + */ + void SetUserData(void* userData) { _userData = userData; } + + /** + Get user data set into the XMLNode. TinyXML-2 in + no way processes or interprets user data. + It is initially 0. + */ + void* GetUserData() const { return _userData; } + +protected: + explicit XMLNode( XMLDocument* ); + virtual ~XMLNode(); + + virtual char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr); + + XMLDocument* _document; + XMLNode* _parent; + mutable StrPair _value; + int _parseLineNum; + + XMLNode* _firstChild; + XMLNode* _lastChild; + + XMLNode* _prev; + XMLNode* _next; + + void* _userData; + +private: + MemPool* _memPool; + void Unlink( XMLNode* child ); + static void DeleteNode( XMLNode* node ); + void InsertChildPreamble( XMLNode* insertThis ) const; + const XMLElement* ToElementWithName( const char* name ) const; + + XMLNode( const XMLNode& ); // not supported + XMLNode& operator=( const XMLNode& ); // not supported +}; + + +/** XML text. + + Note that a text node can have child element nodes, for example: + @verbatim + This is bold + @endverbatim + + A text node can have 2 ways to output the next. "normal" output + and CDATA. It will default to the mode it was parsed from the XML file and + you generally want to leave it alone, but you can change the output mode with + SetCData() and query it with CData(). +*/ +class TINYXML2_LIB XMLText : public XMLNode +{ + friend class XMLDocument; +public: + virtual bool Accept( XMLVisitor* visitor ) const override; + + virtual XMLText* ToText() override { + return this; + } + virtual const XMLText* ToText() const override { + return this; + } + + /// Declare whether this should be CDATA or standard text. + void SetCData( bool isCData ) { + _isCData = isCData; + } + /// Returns true if this is a CDATA text element. + bool CData() const { + return _isCData; + } + + virtual XMLNode* ShallowClone( XMLDocument* document ) const override; + virtual bool ShallowEqual( const XMLNode* compare ) const override; + +protected: + explicit XMLText( XMLDocument* doc ) : XMLNode( doc ), _isCData( false ) {} + virtual ~XMLText() {} + + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ) override; + +private: + bool _isCData; + + XMLText( const XMLText& ); // not supported + XMLText& operator=( const XMLText& ); // not supported +}; + + +/** An XML Comment. */ +class TINYXML2_LIB XMLComment : public XMLNode +{ + friend class XMLDocument; +public: + virtual XMLComment* ToComment() override { + return this; + } + virtual const XMLComment* ToComment() const override { + return this; + } + + virtual bool Accept( XMLVisitor* visitor ) const override; + + virtual XMLNode* ShallowClone( XMLDocument* document ) const override; + virtual bool ShallowEqual( const XMLNode* compare ) const override; + +protected: + explicit XMLComment( XMLDocument* doc ); + virtual ~XMLComment(); + + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr) override; + +private: + XMLComment( const XMLComment& ); // not supported + XMLComment& operator=( const XMLComment& ); // not supported +}; + + +/** In correct XML the declaration is the first entry in the file. + @verbatim + + @endverbatim + + TinyXML-2 will happily read or write files without a declaration, + however. + + The text of the declaration isn't interpreted. It is parsed + and written as a string. +*/ +class TINYXML2_LIB XMLDeclaration : public XMLNode +{ + friend class XMLDocument; +public: + virtual XMLDeclaration* ToDeclaration() override { + return this; + } + virtual const XMLDeclaration* ToDeclaration() const override { + return this; + } + + virtual bool Accept( XMLVisitor* visitor ) const override; + + virtual XMLNode* ShallowClone( XMLDocument* document ) const override; + virtual bool ShallowEqual( const XMLNode* compare ) const override; + +protected: + explicit XMLDeclaration( XMLDocument* doc ); + virtual ~XMLDeclaration(); + + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ) override; + +private: + XMLDeclaration( const XMLDeclaration& ); // not supported + XMLDeclaration& operator=( const XMLDeclaration& ); // not supported +}; + + +/** Any tag that TinyXML-2 doesn't recognize is saved as an + unknown. It is a tag of text, but should not be modified. + It will be written back to the XML, unchanged, when the file + is saved. + + DTD tags get thrown into XMLUnknowns. +*/ +class TINYXML2_LIB XMLUnknown : public XMLNode +{ + friend class XMLDocument; +public: + virtual XMLUnknown* ToUnknown() override { + return this; + } + virtual const XMLUnknown* ToUnknown() const override { + return this; + } + + virtual bool Accept( XMLVisitor* visitor ) const override; + + virtual XMLNode* ShallowClone( XMLDocument* document ) const override; + virtual bool ShallowEqual( const XMLNode* compare ) const override; + +protected: + explicit XMLUnknown( XMLDocument* doc ); + virtual ~XMLUnknown(); + + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ) override; + +private: + XMLUnknown( const XMLUnknown& ); // not supported + XMLUnknown& operator=( const XMLUnknown& ); // not supported +}; + + + +/** An attribute is a name-value pair. Elements have an arbitrary + number of attributes, each with a unique name. + + @note The attributes are not XMLNodes. You may only query the + Next() attribute in a list. +*/ +class TINYXML2_LIB XMLAttribute +{ + friend class XMLElement; +public: + /// The name of the attribute. + const char* Name() const; + + /// The value of the attribute. + const char* Value() const; + + /// Gets the line number the attribute is in, if the document was parsed from a file. + int GetLineNum() const { return _parseLineNum; } + + /// The next attribute in the list. + const XMLAttribute* Next() const { + return _next; + } + + /** IntValue interprets the attribute as an integer, and returns the value. + If the value isn't an integer, 0 will be returned. There is no error checking; + use QueryIntValue() if you need error checking. + */ + int IntValue() const { + int i = 0; + QueryIntValue(&i); + return i; + } + + int64_t Int64Value() const { + int64_t i = 0; + QueryInt64Value(&i); + return i; + } + + uint64_t Unsigned64Value() const { + uint64_t i = 0; + QueryUnsigned64Value(&i); + return i; + } + + /// Query as an unsigned integer. See IntValue() + unsigned UnsignedValue() const { + unsigned i=0; + QueryUnsignedValue( &i ); + return i; + } + /// Query as a boolean. See IntValue() + bool BoolValue() const { + bool b=false; + QueryBoolValue( &b ); + return b; + } + /// Query as a double. See IntValue() + double DoubleValue() const { + double d=0; + QueryDoubleValue( &d ); + return d; + } + /// Query as a float. See IntValue() + float FloatValue() const { + float f=0; + QueryFloatValue( &f ); + return f; + } + + /** QueryIntValue interprets the attribute as an integer, and returns the value + in the provided parameter. The function will return XML_SUCCESS on success, + and XML_WRONG_ATTRIBUTE_TYPE if the conversion is not successful. + */ + XMLError QueryIntValue( int* value ) const; + /// See QueryIntValue + XMLError QueryUnsignedValue( unsigned int* value ) const; + /// See QueryIntValue + XMLError QueryInt64Value(int64_t* value) const; + /// See QueryIntValue + XMLError QueryUnsigned64Value(uint64_t* value) const; + /// See QueryIntValue + XMLError QueryBoolValue( bool* value ) const; + /// See QueryIntValue + XMLError QueryDoubleValue( double* value ) const; + /// See QueryIntValue + XMLError QueryFloatValue( float* value ) const; + + /// Set the attribute to a string value. + void SetAttribute( const char* value ); + /// Set the attribute to value. + void SetAttribute( int value ); + /// Set the attribute to value. + void SetAttribute( unsigned value ); + /// Set the attribute to value. + void SetAttribute(int64_t value); + /// Set the attribute to value. + void SetAttribute(uint64_t value); + /// Set the attribute to value. + void SetAttribute( bool value ); + /// Set the attribute to value. + void SetAttribute( double value ); + /// Set the attribute to value. + void SetAttribute( float value ); + +private: + enum { BUF_SIZE = 200 }; + + XMLAttribute() : _name(), _value(),_parseLineNum( 0 ), _next( 0 ), _memPool( 0 ) {} + virtual ~XMLAttribute() {} + + XMLAttribute( const XMLAttribute& ); // not supported + void operator=( const XMLAttribute& ); // not supported + void SetName( const char* name ); + + char* ParseDeep( char* p, bool processEntities, int* curLineNumPtr ); + + mutable StrPair _name; + mutable StrPair _value; + int _parseLineNum; + XMLAttribute* _next; + MemPool* _memPool; +}; + + +/** The element is a container class. It has a value, the element name, + and can contain other elements, text, comments, and unknowns. + Elements also contain an arbitrary number of attributes. +*/ +class TINYXML2_LIB XMLElement : public XMLNode +{ + friend class XMLDocument; +public: + /// Get the name of an element (which is the Value() of the node.) + const char* Name() const { + return Value(); + } + /// Set the name of the element. + void SetName( const char* str, bool staticMem=false ) { + SetValue( str, staticMem ); + } + + virtual XMLElement* ToElement() override { + return this; + } + virtual const XMLElement* ToElement() const override { + return this; + } + virtual bool Accept( XMLVisitor* visitor ) const override; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none + exists. For example: + + @verbatim + const char* value = ele->Attribute( "foo" ); + @endverbatim + + The 'value' parameter is normally null. However, if specified, + the attribute will only be returned if the 'name' and 'value' + match. This allow you to write code: + + @verbatim + if ( ele->Attribute( "foo", "bar" ) ) callFooIsBar(); + @endverbatim + + rather than: + @verbatim + if ( ele->Attribute( "foo" ) ) { + if ( strcmp( ele->Attribute( "foo" ), "bar" ) == 0 ) callFooIsBar(); + } + @endverbatim + */ + const char* Attribute( const char* name, const char* value=0 ) const; + + /** Given an attribute name, IntAttribute() returns the value + of the attribute interpreted as an integer. The default + value will be returned if the attribute isn't present, + or if there is an error. (For a method with error + checking, see QueryIntAttribute()). + */ + int IntAttribute(const char* name, int defaultValue = 0) const; + /// See IntAttribute() + unsigned UnsignedAttribute(const char* name, unsigned defaultValue = 0) const; + /// See IntAttribute() + int64_t Int64Attribute(const char* name, int64_t defaultValue = 0) const; + /// See IntAttribute() + uint64_t Unsigned64Attribute(const char* name, uint64_t defaultValue = 0) const; + /// See IntAttribute() + bool BoolAttribute(const char* name, bool defaultValue = false) const; + /// See IntAttribute() + double DoubleAttribute(const char* name, double defaultValue = 0) const; + /// See IntAttribute() + float FloatAttribute(const char* name, float defaultValue = 0) const; + + /** Given an attribute name, QueryIntAttribute() returns + XML_SUCCESS, XML_WRONG_ATTRIBUTE_TYPE if the conversion + can't be performed, or XML_NO_ATTRIBUTE if the attribute + doesn't exist. If successful, the result of the conversion + will be written to 'value'. If not successful, nothing will + be written to 'value'. This allows you to provide default + value: + + @verbatim + int value = 10; + QueryIntAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 + @endverbatim + */ + XMLError QueryIntAttribute( const char* name, int* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryIntValue( value ); + } + + /// See QueryIntAttribute() + XMLError QueryUnsignedAttribute( const char* name, unsigned int* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryUnsignedValue( value ); + } + + /// See QueryIntAttribute() + XMLError QueryInt64Attribute(const char* name, int64_t* value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryInt64Value(value); + } + + /// See QueryIntAttribute() + XMLError QueryUnsigned64Attribute(const char* name, uint64_t* value) const { + const XMLAttribute* a = FindAttribute(name); + if(!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryUnsigned64Value(value); + } + + /// See QueryIntAttribute() + XMLError QueryBoolAttribute( const char* name, bool* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryBoolValue( value ); + } + /// See QueryIntAttribute() + XMLError QueryDoubleAttribute( const char* name, double* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryDoubleValue( value ); + } + /// See QueryIntAttribute() + XMLError QueryFloatAttribute( const char* name, float* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryFloatValue( value ); + } + + /// See QueryIntAttribute() + XMLError QueryStringAttribute(const char* name, const char** value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + *value = a->Value(); + return XML_SUCCESS; + } + + + + /** Given an attribute name, QueryAttribute() returns + XML_SUCCESS, XML_WRONG_ATTRIBUTE_TYPE if the conversion + can't be performed, or XML_NO_ATTRIBUTE if the attribute + doesn't exist. It is overloaded for the primitive types, + and is a generally more convenient replacement of + QueryIntAttribute() and related functions. + + If successful, the result of the conversion + will be written to 'value'. If not successful, nothing will + be written to 'value'. This allows you to provide default + value: + + @verbatim + int value = 10; + QueryAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 + @endverbatim + */ + XMLError QueryAttribute( const char* name, int* value ) const { + return QueryIntAttribute( name, value ); + } + + XMLError QueryAttribute( const char* name, unsigned int* value ) const { + return QueryUnsignedAttribute( name, value ); + } + + XMLError QueryAttribute(const char* name, int64_t* value) const { + return QueryInt64Attribute(name, value); + } + + XMLError QueryAttribute(const char* name, uint64_t* value) const { + return QueryUnsigned64Attribute(name, value); + } + + XMLError QueryAttribute( const char* name, bool* value ) const { + return QueryBoolAttribute( name, value ); + } + + XMLError QueryAttribute( const char* name, double* value ) const { + return QueryDoubleAttribute( name, value ); + } + + XMLError QueryAttribute( const char* name, float* value ) const { + return QueryFloatAttribute( name, value ); + } + + XMLError QueryAttribute(const char* name, const char** value) const { + return QueryStringAttribute(name, value); + } + + /// Sets the named attribute to value. + void SetAttribute( const char* name, const char* value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, int value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, unsigned value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + + /// Sets the named attribute to value. + void SetAttribute(const char* name, int64_t value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + + /// Sets the named attribute to value. + void SetAttribute(const char* name, uint64_t value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + + /// Sets the named attribute to value. + void SetAttribute( const char* name, bool value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, double value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, float value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + + /** + Delete an attribute. + */ + void DeleteAttribute( const char* name ); + + /// Return the first attribute in the list. + const XMLAttribute* FirstAttribute() const { + return _rootAttribute; + } + /// Query a specific attribute in the list. + const XMLAttribute* FindAttribute( const char* name ) const; + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, GetText() is limited compared to getting the XMLText child + and accessing it directly. + + If the first child of 'this' is a XMLText, the GetText() + returns the character string of the Text node, else null is returned. + + This is a convenient method for getting the text of simple contained text: + @verbatim + This is text + const char* str = fooElement->GetText(); + @endverbatim + + 'str' will be a pointer to "This is text". + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then the value of str would be null. The first child node isn't a text node, it is + another element. From this XML: + @verbatim + This is text + @endverbatim + GetText() will return "This is ". + */ + const char* GetText() const; + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, SetText() is limited compared to creating an XMLText child + and mutating it directly. + + If the first child of 'this' is a XMLText, SetText() sets its value to + the given string, otherwise it will create a first child that is an XMLText. + + This is a convenient method for setting the text of simple contained text: + @verbatim + This is text + fooElement->SetText( "Hullaballoo!" ); + Hullaballoo! + @endverbatim + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then it will not change "This is text", but rather prefix it with a text element: + @verbatim + Hullaballoo!This is text + @endverbatim + + For this XML: + @verbatim + + @endverbatim + SetText() will generate + @verbatim + Hullaballoo! + @endverbatim + */ + void SetText( const char* inText ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( int value ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( unsigned value ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(int64_t value); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(uint64_t value); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( bool value ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( double value ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( float value ); + + /** + Convenience method to query the value of a child text node. This is probably best + shown by example. Given you have a document is this form: + @verbatim + + 1 + 1.4 + + @endverbatim + + The QueryIntText() and similar functions provide a safe and easier way to get to the + "value" of x and y. + + @verbatim + int x = 0; + float y = 0; // types of x and y are contrived for example + const XMLElement* xElement = pointElement->FirstChildElement( "x" ); + const XMLElement* yElement = pointElement->FirstChildElement( "y" ); + xElement->QueryIntText( &x ); + yElement->QueryFloatText( &y ); + @endverbatim + + @returns XML_SUCCESS (0) on success, XML_CAN_NOT_CONVERT_TEXT if the text cannot be converted + to the requested type, and XML_NO_TEXT_NODE if there is no child text to query. + + */ + XMLError QueryIntText( int* ival ) const; + /// See QueryIntText() + XMLError QueryUnsignedText( unsigned* uval ) const; + /// See QueryIntText() + XMLError QueryInt64Text(int64_t* uval) const; + /// See QueryIntText() + XMLError QueryUnsigned64Text(uint64_t* uval) const; + /// See QueryIntText() + XMLError QueryBoolText( bool* bval ) const; + /// See QueryIntText() + XMLError QueryDoubleText( double* dval ) const; + /// See QueryIntText() + XMLError QueryFloatText( float* fval ) const; + + int IntText(int defaultValue = 0) const; + + /// See QueryIntText() + unsigned UnsignedText(unsigned defaultValue = 0) const; + /// See QueryIntText() + int64_t Int64Text(int64_t defaultValue = 0) const; + /// See QueryIntText() + uint64_t Unsigned64Text(uint64_t defaultValue = 0) const; + /// See QueryIntText() + bool BoolText(bool defaultValue = false) const; + /// See QueryIntText() + double DoubleText(double defaultValue = 0) const; + /// See QueryIntText() + float FloatText(float defaultValue = 0) const; + + /** + Convenience method to create a new XMLElement and add it as last (right) + child of this node. Returns the created and inserted element. + */ + XMLElement* InsertNewChildElement(const char* name); + /// See InsertNewChildElement() + XMLComment* InsertNewComment(const char* comment); + /// See InsertNewChildElement() + XMLText* InsertNewText(const char* text); + /// See InsertNewChildElement() + XMLDeclaration* InsertNewDeclaration(const char* text); + /// See InsertNewChildElement() + XMLUnknown* InsertNewUnknown(const char* text); + + + // internal: + enum ElementClosingType { + OPEN, // + CLOSED, // + CLOSING // + }; + ElementClosingType ClosingType() const { + return _closingType; + } + virtual XMLNode* ShallowClone( XMLDocument* document ) const override; + virtual bool ShallowEqual( const XMLNode* compare ) const override; + +protected: + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ) override; + +private: + XMLElement( XMLDocument* doc ); + virtual ~XMLElement(); + XMLElement( const XMLElement& ); // not supported + void operator=( const XMLElement& ); // not supported + + XMLAttribute* FindOrCreateAttribute( const char* name ); + char* ParseAttributes( char* p, int* curLineNumPtr ); + static void DeleteAttribute( XMLAttribute* attribute ); + XMLAttribute* CreateAttribute(); + + enum { BUF_SIZE = 200 }; + ElementClosingType _closingType; + // The attribute list is ordered; there is no 'lastAttribute' + // because the list needs to be scanned for dupes before adding + // a new attribute. + XMLAttribute* _rootAttribute; +}; + + +enum Whitespace { + PRESERVE_WHITESPACE, + COLLAPSE_WHITESPACE, + PEDANTIC_WHITESPACE +}; + + +/** A Document binds together all the functionality. + It can be saved, loaded, and printed to the screen. + All Nodes are connected and allocated to a Document. + If the Document is deleted, all its Nodes are also deleted. +*/ +class TINYXML2_LIB XMLDocument : public XMLNode +{ + friend class XMLElement; + // Gives access to SetError and Push/PopDepth, but over-access for everything else. + // Wishing C++ had "internal" scope. + friend class XMLNode; + friend class XMLText; + friend class XMLComment; + friend class XMLDeclaration; + friend class XMLUnknown; +public: + /// constructor + XMLDocument( bool processEntities = true, Whitespace whitespaceMode = PRESERVE_WHITESPACE ); + ~XMLDocument(); + + virtual XMLDocument* ToDocument() override { + TIXMLASSERT( this == _document ); + return this; + } + virtual const XMLDocument* ToDocument() const override { + TIXMLASSERT( this == _document ); + return this; + } + + /** + Parse an XML file from a character string. + Returns XML_SUCCESS (0) on success, or + an errorID. + + You may optionally pass in the 'nBytes', which is + the number of bytes which will be parsed. If not + specified, TinyXML-2 will assume 'xml' points to a + null terminated string. + */ + XMLError Parse( const char* xml, size_t nBytes=static_cast(-1) ); + + /** + Load an XML file from disk. + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError LoadFile( const char* filename ); + + /** + Load an XML file from disk. You are responsible + for providing and closing the FILE*. + + NOTE: The file should be opened as binary ("rb") + not text in order for TinyXML-2 to correctly + do newline normalization. + + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError LoadFile( FILE* ); + + /** + Save the XML file to disk. + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError SaveFile( const char* filename, bool compact = false ); + + /** + Save the XML file to disk. You are responsible + for providing and closing the FILE*. + + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError SaveFile( FILE* fp, bool compact = false ); + + bool ProcessEntities() const { + return _processEntities; + } + Whitespace WhitespaceMode() const { + return _whitespaceMode; + } + + /** + Returns true if this document has a leading Byte Order Mark of UTF8. + */ + bool HasBOM() const { + return _writeBOM; + } + /** Sets whether to write the BOM when writing the file. + */ + void SetBOM( bool useBOM ) { + _writeBOM = useBOM; + } + + /** Return the root element of DOM. Equivalent to FirstChildElement(). + To get the first node, use FirstChild(). + */ + XMLElement* RootElement() { + return FirstChildElement(); + } + const XMLElement* RootElement() const { + return FirstChildElement(); + } + + /** Print the Document. If the Printer is not provided, it will + print to stdout. If you provide Printer, this can print to a file: + @verbatim + XMLPrinter printer( fp ); + doc.Print( &printer ); + @endverbatim + + Or you can use a printer to print to memory: + @verbatim + XMLPrinter printer; + doc.Print( &printer ); + // printer.CStr() has a const char* to the XML + @endverbatim + */ + void Print( XMLPrinter* streamer=0 ) const; + virtual bool Accept( XMLVisitor* visitor ) const override; + + /** + Create a new Element associated with + this Document. The memory for the Element + is managed by the Document. + */ + XMLElement* NewElement( const char* name ); + /** + Create a new Comment associated with + this Document. The memory for the Comment + is managed by the Document. + */ + XMLComment* NewComment( const char* comment ); + /** + Create a new Text associated with + this Document. The memory for the Text + is managed by the Document. + */ + XMLText* NewText( const char* text ); + /** + Create a new Declaration associated with + this Document. The memory for the object + is managed by the Document. + + If the 'text' param is null, the standard + declaration is used.: + @verbatim + + @endverbatim + */ + XMLDeclaration* NewDeclaration( const char* text=0 ); + /** + Create a new Unknown associated with + this Document. The memory for the object + is managed by the Document. + */ + XMLUnknown* NewUnknown( const char* text ); + + /** + Delete a node associated with this document. + It will be unlinked from the DOM. + */ + void DeleteNode( XMLNode* node ); + + /// Clears the error flags. + void ClearError(); + + /// Return true if there was an error parsing the document. + bool Error() const { + return _errorID != XML_SUCCESS; + } + /// Return the errorID. + XMLError ErrorID() const { + return _errorID; + } + const char* ErrorName() const; + static const char* ErrorIDToName(XMLError errorID); + + /** Returns a "long form" error description. A hopefully helpful + diagnostic with location, line number, and/or additional info. + */ + const char* ErrorStr() const; + + /// A (trivial) utility function that prints the ErrorStr() to stdout. + void PrintError() const; + + /// Return the line where the error occurred, or zero if unknown. + int ErrorLineNum() const + { + return _errorLineNum; + } + + /// Clear the document, resetting it to the initial state. + void Clear(); + + /** + Copies this document to a target document. + The target will be completely cleared before the copy. + If you want to copy a sub-tree, see XMLNode::DeepClone(). + + NOTE: that the 'target' must be non-null. + */ + void DeepCopy(XMLDocument* target) const; + + // internal + char* Identify( char* p, XMLNode** node, bool first ); + + // internal + void MarkInUse(const XMLNode* const); + + virtual XMLNode* ShallowClone( XMLDocument* /*document*/ ) const override{ + return 0; + } + virtual bool ShallowEqual( const XMLNode* /*compare*/ ) const override{ + return false; + } + +private: + XMLDocument( const XMLDocument& ); // not supported + void operator=( const XMLDocument& ); // not supported + + bool _writeBOM; + bool _processEntities; + XMLError _errorID; + Whitespace _whitespaceMode; + mutable StrPair _errorStr; + int _errorLineNum; + char* _charBuffer; + int _parseCurLineNum; + int _parsingDepth; + // Memory tracking does add some overhead. + // However, the code assumes that you don't + // have a bunch of unlinked nodes around. + // Therefore it takes less memory to track + // in the document vs. a linked list in the XMLNode, + // and the performance is the same. + DynArray _unlinked; + + MemPoolT< sizeof(XMLElement) > _elementPool; + MemPoolT< sizeof(XMLAttribute) > _attributePool; + MemPoolT< sizeof(XMLText) > _textPool; + MemPoolT< sizeof(XMLComment) > _commentPool; + + static const char* _errorNames[XML_ERROR_COUNT]; + + void Parse(); + + void SetError( XMLError error, int lineNum, const char* format, ... ); + + // Something of an obvious security hole, once it was discovered. + // Either an ill-formed XML or an excessively deep one can overflow + // the stack. Track stack depth, and error out if needed. + class DepthTracker { + public: + explicit DepthTracker(XMLDocument * document) { + this->_document = document; + document->PushDepth(); + } + ~DepthTracker() { + _document->PopDepth(); + } + private: + XMLDocument * _document; + }; + void PushDepth(); + void PopDepth(); + + template + NodeType* CreateUnlinkedNode( MemPoolT& pool ); +}; + +template +inline NodeType* XMLDocument::CreateUnlinkedNode( MemPoolT& pool ) +{ + TIXMLASSERT( sizeof( NodeType ) == PoolElementSize ); + TIXMLASSERT( sizeof( NodeType ) == pool.ItemSize() ); + NodeType* returnNode = new (pool.Alloc()) NodeType( this ); + TIXMLASSERT( returnNode ); + returnNode->_memPool = &pool; + + _unlinked.Push(returnNode); + return returnNode; +} + +/** + A XMLHandle is a class that wraps a node pointer with null checks; this is + an incredibly useful thing. Note that XMLHandle is not part of the TinyXML-2 + DOM structure. It is a separate utility class. + + Take an example: + @verbatim + + + + + + + @endverbatim + + Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very + easy to write a *lot* of code that looks like: + + @verbatim + XMLElement* root = document.FirstChildElement( "Document" ); + if ( root ) + { + XMLElement* element = root->FirstChildElement( "Element" ); + if ( element ) + { + XMLElement* child = element->FirstChildElement( "Child" ); + if ( child ) + { + XMLElement* child2 = child->NextSiblingElement( "Child" ); + if ( child2 ) + { + // Finally do something useful. + @endverbatim + + And that doesn't even cover "else" cases. XMLHandle addresses the verbosity + of such code. A XMLHandle checks for null pointers so it is perfectly safe + and correct to use: + + @verbatim + XMLHandle docHandle( &document ); + XMLElement* child2 = docHandle.FirstChildElement( "Document" ).FirstChildElement( "Element" ).FirstChildElement().NextSiblingElement(); + if ( child2 ) + { + // do something useful + @endverbatim + + Which is MUCH more concise and useful. + + It is also safe to copy handles - internally they are nothing more than node pointers. + @verbatim + XMLHandle handleCopy = handle; + @endverbatim + + See also XMLConstHandle, which is the same as XMLHandle, but operates on const objects. +*/ +class TINYXML2_LIB XMLHandle +{ +public: + /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. + explicit XMLHandle( XMLNode* node ) : _node( node ) { + } + /// Create a handle from a node. + explicit XMLHandle( XMLNode& node ) : _node( &node ) { + } + /// Copy constructor + XMLHandle( const XMLHandle& ref ) : _node( ref._node ) { + } + /// Assignment + XMLHandle& operator=( const XMLHandle& ref ) { + _node = ref._node; + return *this; + } + + /// Get the first child of this handle. + XMLHandle FirstChild() { + return XMLHandle( _node ? _node->FirstChild() : 0 ); + } + /// Get the first child element of this handle. + XMLHandle FirstChildElement( const char* name = 0 ) { + return XMLHandle( _node ? _node->FirstChildElement( name ) : 0 ); + } + /// Get the last child of this handle. + XMLHandle LastChild() { + return XMLHandle( _node ? _node->LastChild() : 0 ); + } + /// Get the last child element of this handle. + XMLHandle LastChildElement( const char* name = 0 ) { + return XMLHandle( _node ? _node->LastChildElement( name ) : 0 ); + } + /// Get the previous sibling of this handle. + XMLHandle PreviousSibling() { + return XMLHandle( _node ? _node->PreviousSibling() : 0 ); + } + /// Get the previous sibling element of this handle. + XMLHandle PreviousSiblingElement( const char* name = 0 ) { + return XMLHandle( _node ? _node->PreviousSiblingElement( name ) : 0 ); + } + /// Get the next sibling of this handle. + XMLHandle NextSibling() { + return XMLHandle( _node ? _node->NextSibling() : 0 ); + } + /// Get the next sibling element of this handle. + XMLHandle NextSiblingElement( const char* name = 0 ) { + return XMLHandle( _node ? _node->NextSiblingElement( name ) : 0 ); + } + + /// Safe cast to XMLNode. This can return null. + XMLNode* ToNode() { + return _node; + } + /// Safe cast to XMLElement. This can return null. + XMLElement* ToElement() { + return ( _node ? _node->ToElement() : 0 ); + } + /// Safe cast to XMLText. This can return null. + XMLText* ToText() { + return ( _node ? _node->ToText() : 0 ); + } + /// Safe cast to XMLUnknown. This can return null. + XMLUnknown* ToUnknown() { + return ( _node ? _node->ToUnknown() : 0 ); + } + /// Safe cast to XMLDeclaration. This can return null. + XMLDeclaration* ToDeclaration() { + return ( _node ? _node->ToDeclaration() : 0 ); + } + +private: + XMLNode* _node; +}; + + +/** + A variant of the XMLHandle class for working with const XMLNodes and Documents. It is the + same in all regards, except for the 'const' qualifiers. See XMLHandle for API. +*/ +class TINYXML2_LIB XMLConstHandle +{ +public: + explicit XMLConstHandle( const XMLNode* node ) : _node( node ) { + } + explicit XMLConstHandle( const XMLNode& node ) : _node( &node ) { + } + XMLConstHandle( const XMLConstHandle& ref ) : _node( ref._node ) { + } + + XMLConstHandle& operator=( const XMLConstHandle& ref ) { + _node = ref._node; + return *this; + } + + const XMLConstHandle FirstChild() const { + return XMLConstHandle( _node ? _node->FirstChild() : 0 ); + } + const XMLConstHandle FirstChildElement( const char* name = 0 ) const { + return XMLConstHandle( _node ? _node->FirstChildElement( name ) : 0 ); + } + const XMLConstHandle LastChild() const { + return XMLConstHandle( _node ? _node->LastChild() : 0 ); + } + const XMLConstHandle LastChildElement( const char* name = 0 ) const { + return XMLConstHandle( _node ? _node->LastChildElement( name ) : 0 ); + } + const XMLConstHandle PreviousSibling() const { + return XMLConstHandle( _node ? _node->PreviousSibling() : 0 ); + } + const XMLConstHandle PreviousSiblingElement( const char* name = 0 ) const { + return XMLConstHandle( _node ? _node->PreviousSiblingElement( name ) : 0 ); + } + const XMLConstHandle NextSibling() const { + return XMLConstHandle( _node ? _node->NextSibling() : 0 ); + } + const XMLConstHandle NextSiblingElement( const char* name = 0 ) const { + return XMLConstHandle( _node ? _node->NextSiblingElement( name ) : 0 ); + } + + + const XMLNode* ToNode() const { + return _node; + } + const XMLElement* ToElement() const { + return ( _node ? _node->ToElement() : 0 ); + } + const XMLText* ToText() const { + return ( _node ? _node->ToText() : 0 ); + } + const XMLUnknown* ToUnknown() const { + return ( _node ? _node->ToUnknown() : 0 ); + } + const XMLDeclaration* ToDeclaration() const { + return ( _node ? _node->ToDeclaration() : 0 ); + } + +private: + const XMLNode* _node; +}; + + +/** + Printing functionality. The XMLPrinter gives you more + options than the XMLDocument::Print() method. + + It can: + -# Print to memory. + -# Print to a file you provide. + -# Print XML without a XMLDocument. + + Print to Memory + + @verbatim + XMLPrinter printer; + doc.Print( &printer ); + SomeFunction( printer.CStr() ); + @endverbatim + + Print to a File + + You provide the file pointer. + @verbatim + XMLPrinter printer( fp ); + doc.Print( &printer ); + @endverbatim + + Print without a XMLDocument + + When loading, an XML parser is very useful. However, sometimes + when saving, it just gets in the way. The code is often set up + for streaming, and constructing the DOM is just overhead. + + The Printer supports the streaming case. The following code + prints out a trivially simple XML file without ever creating + an XML document. + + @verbatim + XMLPrinter printer( fp ); + printer.OpenElement( "foo" ); + printer.PushAttribute( "foo", "bar" ); + printer.CloseElement(); + @endverbatim +*/ +class TINYXML2_LIB XMLPrinter : public XMLVisitor +{ +public: + /** Construct the printer. If the FILE* is specified, + this will print to the FILE. Else it will print + to memory, and the result is available in CStr(). + If 'compact' is set to true, then output is created + with only required whitespace and newlines. + */ + XMLPrinter( FILE* file=0, bool compact = false, int depth = 0 ); + virtual ~XMLPrinter() {} + + /** If streaming, write the BOM and declaration. */ + void PushHeader( bool writeBOM, bool writeDeclaration ); + /** If streaming, start writing an element. + The element must be closed with CloseElement() + */ + void OpenElement( const char* name, bool compactMode=false ); + /// If streaming, add an attribute to an open element. + void PushAttribute( const char* name, const char* value ); + void PushAttribute( const char* name, int value ); + void PushAttribute( const char* name, unsigned value ); + void PushAttribute( const char* name, int64_t value ); + void PushAttribute( const char* name, uint64_t value ); + void PushAttribute( const char* name, bool value ); + void PushAttribute( const char* name, double value ); + /// If streaming, close the Element. + virtual void CloseElement( bool compactMode=false ); + + /// Add a text node. + void PushText( const char* text, bool cdata=false ); + /// Add a text node from an integer. + void PushText( int value ); + /// Add a text node from an unsigned. + void PushText( unsigned value ); + /// Add a text node from a signed 64bit integer. + void PushText( int64_t value ); + /// Add a text node from an unsigned 64bit integer. + void PushText( uint64_t value ); + /// Add a text node from a bool. + void PushText( bool value ); + /// Add a text node from a float. + void PushText( float value ); + /// Add a text node from a double. + void PushText( double value ); + + /// Add a comment + void PushComment( const char* comment ); + + void PushDeclaration( const char* value ); + void PushUnknown( const char* value ); + + virtual bool VisitEnter( const XMLDocument& /*doc*/ ) override; + virtual bool VisitExit( const XMLDocument& /*doc*/ ) override { + return true; + } + + virtual bool VisitEnter( const XMLElement& element, const XMLAttribute* attribute ) override; + virtual bool VisitExit( const XMLElement& element ) override; + + virtual bool Visit( const XMLText& text ) override; + virtual bool Visit( const XMLComment& comment ) override; + virtual bool Visit( const XMLDeclaration& declaration ) override; + virtual bool Visit( const XMLUnknown& unknown ) override; + + /** + If in print to memory mode, return a pointer to + the XML file in memory. + */ + const char* CStr() const { + return _buffer.Mem(); + } + /** + If in print to memory mode, return the size + of the XML file in memory. (Note the size returned + includes the terminating null.) + */ + int CStrSize() const { + return _buffer.Size(); + } + /** + If in print to memory mode, reset the buffer to the + beginning. + */ + void ClearBuffer( bool resetToFirstElement = true ) { + _buffer.Clear(); + _buffer.Push(0); + _firstElement = resetToFirstElement; + } + +protected: + virtual bool CompactMode( const XMLElement& ) { return _compactMode; } + + /** Prints out the space before an element. You may override to change + the space and tabs used. A PrintSpace() override should call Print(). + */ + virtual void PrintSpace( int depth ); + virtual void Print( const char* format, ... ); + virtual void Write( const char* data, size_t size ); + virtual void Putc( char ch ); + + inline void Write(const char* data) { Write(data, strlen(data)); } + + void SealElementIfJustOpened(); + bool _elementJustOpened; + DynArray< const char*, 10 > _stack; + +private: + /** + Prepares to write a new node. This includes sealing an element that was + just opened, and writing any whitespace necessary if not in compact mode. + */ + void PrepareForNewNode( bool compactMode ); + void PrintString( const char*, bool restrictedEntitySet ); // prints out, after detecting entities. + + bool _firstElement; + FILE* _fp; + int _depth; + int _textDepth; + bool _processEntities; + bool _compactMode; + + enum { + ENTITY_RANGE = 64, + BUF_SIZE = 200 + }; + bool _entityFlag[ENTITY_RANGE]; + bool _restrictedEntityFlag[ENTITY_RANGE]; + + DynArray< char, 20 > _buffer; + + // Prohibit cloning, intentionally not implemented + XMLPrinter( const XMLPrinter& ); + XMLPrinter& operator=( const XMLPrinter& ); +}; + + +} // tinyxml2 + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif // TINYXML2_INCLUDED diff --git a/src/controller/AsyncModuleController.hpp b/src/controller/AsyncModuleController.hpp index 5cfc7a92..7ad6b131 100644 --- a/src/controller/AsyncModuleController.hpp +++ b/src/controller/AsyncModuleController.hpp @@ -206,6 +206,7 @@ class ModuleController : public oatpp::web::server::api::ApiController res->error = "ModuleError"; res->message = fmt::format("Failed to disable module: {}", plugin_name); } + return _return(controller->createDtoResponse(Status::CODE_200, res)); } }; diff --git a/src/core/plugin/CMakeLists.txt b/src/core/plugin/CMakeLists.txt index 931bcac5..1733f82a 100644 --- a/src/core/plugin/CMakeLists.txt +++ b/src/core/plugin/CMakeLists.txt @@ -4,12 +4,14 @@ project(lithiumplugin C CXX) list(APPEND ${PROJECT_NAME}_SOURCES exe_plugin.cpp + wrapper_plugin.cpp plugin.cpp ) # Headers list(APPEND ${PROJECT_NAME}_HEADERS exe_plugin.hpp + wrapper_plugin.hpp plugin.hpp ) diff --git a/src/core/plugin/plugin_info.cpp b/src/core/plugin/plugin_info.cpp new file mode 100644 index 00000000..aaf9e1df --- /dev/null +++ b/src/core/plugin/plugin_info.cpp @@ -0,0 +1,126 @@ +/* + * plugin_info.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-8-6 + +Description: Basic Plugin Infomation + +**************************************************/ + +#include "plugin_info.hpp" + +#include + +#include "atom/log/loguru.hpp" + +PackageInfo::PackageInfo(const std::string &filename) : filename_(filename) {} + +void PackageInfo::loadPackageJson() +{ + std::ifstream file(filename_); + if (file.is_open()) + { + json j; + file >> j; + file.close(); + package_ = j; + DLOG_F(INFO, "Loaded {}", filename_); + } + else + { + LOG_F(ERROR, "Failed to open file {}", filename_); + } +} + +void PackageInfo::savePackageJson() +{ + std::ofstream file(filename_); + if (file.is_open()) + { + json j = package_; + file << j.dump(4); + file.close(); + std::cout << "Saved " << filename_ << std::endl; + } + else + { + std::cerr << "Failed to open file " << filename_ << std::endl; + } +} + +std::string PackageInfo::getName() const +{ + return package_.at("name").get(); +} + +std::string PackageInfo::getVersion() const +{ + return package_.at("version").get(); +} + +bool PackageInfo::isPrivate() const +{ + return package_.at("private").get(); +} + +void PackageInfo::setName(const std::string &name) +{ + package_["name"] = name; +} + +void PackageInfo::setVersion(const std::string &version) +{ + package_["version"] = version; +} + +void PackageInfo::setIsPrivate(bool isPrivate) +{ + package_["private"] = isPrivate; +} + +_PackageJson PackageInfo::toStruct() const +{ + _PackageJson result; + result.name = getName(); + result.version = getVersion(); + result.isPrivate = isPrivate(); + + result.scripts.dev = package_["scripts"].value("dev", ""); + result.scripts.build = package_["scripts"].value("build", ""); + result.scripts.start = package_["scripts"].value("start", ""); + result.scripts.lint = package_["scripts"].value("lint", ""); + + for (const auto &dep : package_["dependencies"].items()) + { + result.dependencies.regular[dep.key()] = dep.value().get(); + } + + for (const auto &devDep : package_["devDependencies"].items()) + { + result.devDependencies.regular[devDep.key()] = devDep.value().get(); + } + + return result; +} diff --git a/src/core/plugin/plugin_info.hpp b/src/core/plugin/plugin_info.hpp new file mode 100644 index 00000000..899fcf34 --- /dev/null +++ b/src/core/plugin/plugin_info.hpp @@ -0,0 +1,155 @@ +/* + * plugin_info.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-8-6 + +Description: Basic Plugin Infomation + +**************************************************/ + +#pragma once + +#include +#include +#include + +#include "atom/type/json.hpp" + +using json = nlohmann::json; + +struct _Scripts +{ + std::string dev; /**< Development script */ + std::string build; /**< Build script */ + std::string start; /**< Start script */ + std::string lint; /**< Lint script */ +}; + +struct _Dependencies +{ + std::unordered_map regular; /**< Regular dependencies */ + std::unordered_map dev; /**< Development dependencies */ +}; + +struct _PackageJson +{ + std::string name; /**< Package name */ + std::string version; /**< Package version */ + bool isPrivate; /**< Is the package private? */ + _Scripts scripts; /**< Scripts section */ + _Dependencies dependencies; /**< Regular dependencies */ + _Dependencies devDependencies; /**< Development dependencies */ +}; + +/** + * @brief The PackageInfo class provides functionality to load and save package.json files, + * as well as access and modify package information. + */ +class PackageInfo +{ +public: + /** + * @brief Constructs a PackageInfo object with the specified filename. + * @param filename The name of the package.json file. + */ + PackageInfo(const std::string &filename); + + /** + * @brief Loads the package.json file. + */ + void loadPackageJson(); + + /** + * @brief Saves the package.json file. + */ + void savePackageJson(); + + /** + * @brief Gets the name of the package. + * @return The name of the package. + */ + std::string getName() const; + + /** + * @brief Gets the version of the package. + * @return The version of the package. + */ + std::string getVersion() const; + + /** + * @brief Checks if the package is private. + * @return True if the package is private, false otherwise. + */ + bool isPrivate() const; + + /** + * @brief Sets the name of the package. + * @param name The name of the package. + */ + void setName(const std::string &name); + + /** + * @brief Sets the version of the package. + * @param version The version of the package. + */ + void setVersion(const std::string &version); + + /** + * @brief Sets whether the package is private. + * @param isPrivate True if the package is private, false otherwise. + */ + void setIsPrivate(bool isPrivate); + + /** + * @brief Converts the package.json data to a _PackageJson struct. + * @return The package.json data as a _PackageJson struct. + */ + _PackageJson toStruct() const; + +private: + std::string filename_; /**< The filename of the package.json file. */ + json package_; /**< The parsed package.json data. */ +}; + +/* +int main() +{ + PackageInfo manager("package.json"); + manager.loadPackageJson(); + + std::cout << "Name: " << manager.getName() << std::endl; + std::cout << "Version: " << manager.getVersion() << std::endl; + std::cout << "Private: " << std::boolalpha << manager.isPrivate() << std::endl; + + manager.setName("cobalt-web"); + manager.setVersion("0.1.0"); + manager.setIsPrivate(true); + + manager.savePackageJson(); + + return 0; +} + +*/ diff --git a/src/core/plugin/wrapper_plugin.cpp b/src/core/plugin/wrapper_plugin.cpp new file mode 100644 index 00000000..9b88c628 --- /dev/null +++ b/src/core/plugin/wrapper_plugin.cpp @@ -0,0 +1,13 @@ +#include "wrapper_plugin.hpp" + +#ifdef _WIN32 +const std::string CHECK_COMMAND = "where"; +#else +const std::string CHECK_COMMAND = "which"; +#endif + +WrapperPlugin::WrapperPlugin(const std::string &path, const std::string &version, const std::string &author, const std::string &description) + : Plugin(path, version, author, description) +{ + +} \ No newline at end of file diff --git a/src/core/plugin/wrapper_plugin.hpp b/src/core/plugin/wrapper_plugin.hpp new file mode 100644 index 00000000..efddec8e --- /dev/null +++ b/src/core/plugin/wrapper_plugin.hpp @@ -0,0 +1,40 @@ +/* + * wrapper_plugin.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-8 + +Description: Wrapper Executable Plugin. This means to give some functions to control a standalone software + +**************************************************/ + +#pragma once + +#include "plugin.hpp" + +class WrapperPlugin : public Plugin +{ +public: + WrapperPlugin(const std::string &path, const std::string &version, const std::string &author, const std::string &description); +}; diff --git a/src/device/device_manager.cpp b/src/device/device_manager.cpp index 51f82be0..bad49336 100644 --- a/src/device/device_manager.cpp +++ b/src/device/device_manager.cpp @@ -34,6 +34,7 @@ Description: Device Manager #include "atom/plugin/module_loader.hpp" #include "server/message_bus.hpp" #include "thread/thread.hpp" +#include "atom/server/global_ptr.hpp" #include "core/camera.hpp" #include "core/telescope.hpp" @@ -105,7 +106,7 @@ namespace Lithium // Constructor DeviceManager::DeviceManager(std::shared_ptr messageBus, std::shared_ptr configManager) { - m_ModuleLoader = ModuleLoader::createShared("drivers"); + m_ModuleLoader = ModuleLoader::createShared("drivers", GetPtr("ThreadManager")); m_ConfigManager = configManager; m_MessageBus = messageBus; for (auto &devices : m_devices) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index b97b0737..7bf94315 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -13,11 +13,11 @@ target_link_libraries(json2ini loguru) add_executable(json2xml json2xml.cpp) target_link_libraries(json2xml loguru) -target_link_libraries(json2xml pugixml-shared) +target_link_libraries(json2xml tinyxml2) add_executable(xml2json xml2json.cpp) target_link_libraries(xml2json loguru) -target_link_libraries(xml2json pugixml-shared) +target_link_libraries(xml2json tinyxml2) add_executable(csv2json csv2json.cpp) target_link_libraries(csv2json loguru) diff --git a/tools/csv2json.cpp b/tools/csv2json.cpp index f5d57170..15e9eb48 100644 --- a/tools/csv2json.cpp +++ b/tools/csv2json.cpp @@ -1,10 +1,41 @@ +/* + * csv2json.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-7 + +Description: CSV to JSON conversion + +**************************************************/ + #include #include #include #include #include #include -#include "nlohmann/json.hpp" +#include "atom/type/json.hpp" #include "argparse/argparse.hpp" #include "atom/log/loguru.hpp" diff --git a/tools/json2ini.cpp b/tools/json2ini.cpp index 5d7fc532..2be72ad0 100644 --- a/tools/json2ini.cpp +++ b/tools/json2ini.cpp @@ -1,7 +1,37 @@ -#include +/* + * json2ini.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-7-29 + +Description: JSON to INI + +**************************************************/ + #include #include -#include +#include "atom/type/json.hpp" #include "atom/log/loguru.hpp" using json = nlohmann::json; diff --git a/tools/json2xml.cpp b/tools/json2xml.cpp index 970aef94..08c06ddb 100644 --- a/tools/json2xml.cpp +++ b/tools/json2xml.cpp @@ -1,70 +1,103 @@ -#include +/* + * json2xml.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-7 + +Description: Json to XML conversion + +**************************************************/ + #include #include -#include "nlohmann/json.hpp" -#include "pugixml/pugixml.hpp" +#include "atom/type/json.hpp" +#include "atom/type/tinyxml2.h" #include "atom/log/loguru.hpp" #include using json = nlohmann::json; -void jsonToXml(const json &jsonData, pugi::xml_node &xmlNode) +void jsonToXml(const json &jsonData, tinyxml2::XMLElement *xmlElement) { + tinyxml2::XMLDocument *xmlDoc = xmlElement->GetDocument(); + for (const auto &item : jsonData.items()) { if (item.value().is_object()) { - pugi::xml_node childXmlNode = xmlNode.append_child(item.key().c_str()); - jsonToXml(item.value(), childXmlNode); + tinyxml2::XMLElement *childXmlElement = xmlDoc->NewElement(item.key().c_str()); + xmlElement->InsertEndChild(childXmlElement); + jsonToXml(item.value(), childXmlElement); } else if (item.value().is_array()) { for (const auto &arrayItem : item.value()) { - pugi::xml_node childXmlNode = xmlNode.append_child(item.key().c_str()); - jsonToXml(arrayItem, childXmlNode); + tinyxml2::XMLElement *childXmlElement = xmlDoc->NewElement(item.key().c_str()); + xmlElement->InsertEndChild(childXmlElement); + jsonToXml(arrayItem, childXmlElement); } } else { - xmlNode.append_child(item.key().c_str()).text().set(item.value().get().c_str()); + tinyxml2::XMLElement *childXmlElement = xmlDoc->NewElement(item.key().c_str()); + childXmlElement->SetText(item.value().get().c_str()); + xmlElement->InsertEndChild(childXmlElement); } } } bool convertJsonToXml(const std::string &jsonFilePath, const std::string &xmlFilePath) { - // 设置 loguru 的日志文件 loguru::add_file("conversion.log", loguru::Append, loguru::Verbosity_INFO); + DLOG_F(INFO, "Reading JSON file: {}", jsonFilePath); // 读取 JSON 文件 - DLOG_F(INFO, "Reading JSON file: %s", jsonFilePath.c_str()); std::ifstream jsonFile(jsonFilePath); if (!jsonFile.is_open()) { - DLOG_F(ERROR, "Failed to open JSON file: %s", jsonFilePath.c_str()); + std::cout << "Failed to open JSON file: " << jsonFilePath << std::endl; return false; } // 解析 JSON - DLOG_F(INFO, "Parsing JSON data"); json jsonData; jsonFile >> jsonData; jsonFile.close(); // 创建 XML 文档 - DLOG_F(INFO, "Creating XML document"); - pugi::xml_document xmlDoc; + tinyxml2::XMLDocument xmlDoc; + tinyxml2::XMLElement *rootElement = xmlDoc.NewElement("root"); + xmlDoc.InsertFirstChild(rootElement); // 转换 JSON 到 XML - DLOG_F(INFO, "Converting JSON to XML"); - jsonToXml(jsonData, xmlDoc); + jsonToXml(jsonData, rootElement); // 保存 XML 文档到文件 - DLOG_F(INFO, "Saving XML file: %s", xmlFilePath.c_str()); - if (!xmlDoc.save_file(xmlFilePath.c_str())) + if (xmlDoc.SaveFile(xmlFilePath.c_str()) != tinyxml2::XML_SUCCESS) { - DLOG_F(ERROR, "Failed to save XML file: %s", xmlFilePath.c_str()); + LOG_F(ERROR, "Failed to save XML file: {}", xmlFilePath); return false; } diff --git a/tools/xml2json.cpp b/tools/xml2json.cpp index 5d259d97..8388315b 100644 --- a/tools/xml2json.cpp +++ b/tools/xml2json.cpp @@ -1,20 +1,50 @@ -#include +/* + * xml2json.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-7 + +Description: XML to JSON conversion + +**************************************************/ + #include #include -#include "nlohmann/json.hpp" -#include "pugixml/pugixml.hpp" +#include "atom/type/json.hpp" +#include "atom/type/tinyxml2.h" #include "atom/log/loguru.hpp" #include using json = nlohmann::json; -void xmlToJson(const pugi::xml_node &xmlNode, json &jsonData) +void xmlToJson(const tinyxml2::XMLElement *xmlElement, json &jsonData) { - for (const auto &childXmlNode : xmlNode.children()) + for (const tinyxml2::XMLNode *childNode = xmlElement->FirstChild(); childNode != nullptr; childNode = childNode->NextSibling()) { - if (childXmlNode.type() == pugi::node_element) + if (childNode->ToElement()) { - const std::string childNodeName = childXmlNode.name(); + const std::string childNodeName = childNode->Value(); json &jsonChildValue = jsonData[childNodeName]; if (!jsonChildValue.is_null()) { @@ -30,12 +60,12 @@ void xmlToJson(const pugi::xml_node &xmlNode, json &jsonData) } json jsonItemValue; - xmlToJson(childXmlNode, jsonItemValue); + xmlToJson(childNode->ToElement(), jsonItemValue); jsonChildValue.push_back(jsonItemValue); } - else if (childXmlNode.type() == pugi::node_pcdata) + else if (childNode->ToText()) { - jsonData = json(childXmlNode.value()); + jsonData = json(childNode->ToText()->Value()); } } } @@ -46,11 +76,11 @@ bool convertXmlToJson(const std::string &xmlFilePath, const std::string &jsonFil loguru::add_file("conversion.log", loguru::Append, loguru::Verbosity_INFO); // 读取 XML 文件 - DLOG_F(INFO, "Reading XML file: %s", xmlFilePath.c_str()); - pugi::xml_document xmlDoc; - if (!xmlDoc.load_file(xmlFilePath.c_str())) + DLOG_F(INFO, "Reading XML file: {}", xmlFilePath); + tinyxml2::XMLDocument xmlDoc; + if (xmlDoc.LoadFile(xmlFilePath.c_str()) != tinyxml2::XML_SUCCESS) { - DLOG_F(ERROR, "Failed to load XML file: %s", xmlFilePath.c_str()); + DLOG_F(ERROR, "Failed to load XML file: {}", xmlFilePath); return false; } @@ -59,14 +89,14 @@ bool convertXmlToJson(const std::string &xmlFilePath, const std::string &jsonFil // 转换 XML 到 JSON DLOG_F(INFO, "Converting XML to JSON"); - xmlToJson(xmlDoc, jsonData); + xmlToJson(xmlDoc.RootElement(), jsonData); // 保存 JSON 对象到文件 - DLOG_F(INFO, "Saving JSON file: %s", jsonFilePath.c_str()); + DLOG_F(INFO, "Saving JSON file: {}", jsonFilePath); std::ofstream jsonFile(jsonFilePath); if (!jsonFile.is_open()) { - DLOG_F(ERROR, "Failed to open JSON file: %s", jsonFilePath.c_str()); + DLOG_F(ERROR, "Failed to open JSON file: {}", jsonFilePath); return false; } From 6b4357e542e4cdab14c2227cd695f93e1e8b295e Mon Sep 17 00:00:00 2001 From: Max Qian Date: Sat, 9 Dec 2023 19:23:50 +0800 Subject: [PATCH 5/9] update --- libs | 2 +- modules | 2 +- src/core/CMakeLists.txt | 34 - src/core/base/CMakeLists.txt | 113 - src/core/base/base64.cpp | 170 - src/core/base/base64.hpp | 65 - src/core/base/base64_luts.hpp | 4252 ------------------ src/core/base/basedevice.cpp | 993 ---- src/core/base/basedevice.h | 314 -- src/core/base/basedevice_p.h | 152 - src/core/base/hydrogenapi.h.in | 520 --- src/core/base/hydrogenbase.cpp | 47 - src/core/base/hydrogenbase.h | 131 - src/core/base/hydrogenbasetypes.h | 83 - src/core/base/hydrogencom.cpp | 1899 -------- src/core/base/hydrogencom.hpp | 542 --- src/core/base/hydrogendevapi.cpp | 907 ---- src/core/base/hydrogendevapi.hpp | 830 ---- src/core/base/hydrogendevapis.cpp | 82 - src/core/base/hydrogendevapis.hpp | 59 - src/core/base/hydrogenlilxml.hpp | 522 --- src/core/base/hydrogenstandardproperty.cpp | 34 - src/core/base/hydrogenstandardproperty.h | 159 - src/core/base/hydrogenuserio.cpp | 786 ---- src/core/base/hydrogenuserio.hpp | 87 - src/core/base/hydrogenutility.cpp | 105 - src/core/base/hydrogenutility.hpp | 112 - src/core/base/libastro.cpp | 167 - src/core/base/libastro.hpp | 126 - src/core/base/lilxml.cpp | 1523 ------- src/core/base/lilxml.hpp | 342 -- src/core/base/parentdevice.cpp | 59 - src/core/base/parentdevice.h | 43 - src/core/base/parentdevice_p.h | 15 - src/core/base/sharedblob.cpp | 548 --- src/core/base/sharedblob.hpp | 43 - src/core/base/sharedblob_parse.cpp | 90 - src/core/base/sharedblob_parse.hpp | 36 - src/core/base/shm_open_anon.cpp | 152 - src/core/base/shm_open_anon.hpp | 15 - src/core/base/userio.cpp | 188 - src/core/base/userio.hpp | 63 - src/core/base/watchdeviceproperty.cpp | 155 - src/core/base/watchdeviceproperty.h | 98 - src/core/locale/locale_compat.hpp | 151 - src/core/property/hydrogenproperties.cpp | 221 - src/core/property/hydrogenproperties.h | 112 - src/core/property/hydrogenproperties_p.h | 47 - src/core/property/hydrogenproperty.cpp | 588 --- src/core/property/hydrogenproperty.h | 251 -- src/core/property/hydrogenproperty_p.h | 69 - src/core/property/hydrogenpropertybasic.cpp | 464 -- src/core/property/hydrogenpropertybasic.h | 169 - src/core/property/hydrogenpropertybasic_p.h | 62 - src/core/property/hydrogenpropertyblob.cpp | 79 - src/core/property/hydrogenpropertyblob.h | 55 - src/core/property/hydrogenpropertyblob_p.h | 43 - src/core/property/hydrogenpropertylight.cpp | 57 - src/core/property/hydrogenpropertylight.h | 43 - src/core/property/hydrogenpropertylight_p.h | 42 - src/core/property/hydrogenpropertynumber.cpp | 69 - src/core/property/hydrogenpropertynumber.h | 46 - src/core/property/hydrogenpropertynumber_p.h | 42 - src/core/property/hydrogenpropertyswitch.cpp | 122 - src/core/property/hydrogenpropertyswitch.h | 70 - src/core/property/hydrogenpropertyswitch_p.h | 46 - src/core/property/hydrogenpropertytext.cpp | 63 - src/core/property/hydrogenpropertytext.h | 43 - src/core/property/hydrogenpropertytext_p.h | 42 - src/core/property/hydrogenpropertyview.cpp | 298 -- src/core/property/hydrogenpropertyview.h | 1430 ------ src/core/property/hydrogenwidgettraits.h | 90 - src/core/property/hydrogenwidgetview.h | 19 - 73 files changed, 2 insertions(+), 21496 deletions(-) delete mode 100644 src/core/base/CMakeLists.txt delete mode 100644 src/core/base/base64.cpp delete mode 100644 src/core/base/base64.hpp delete mode 100644 src/core/base/base64_luts.hpp delete mode 100644 src/core/base/basedevice.cpp delete mode 100644 src/core/base/basedevice.h delete mode 100644 src/core/base/basedevice_p.h delete mode 100644 src/core/base/hydrogenapi.h.in delete mode 100644 src/core/base/hydrogenbase.cpp delete mode 100644 src/core/base/hydrogenbase.h delete mode 100644 src/core/base/hydrogenbasetypes.h delete mode 100644 src/core/base/hydrogencom.cpp delete mode 100644 src/core/base/hydrogencom.hpp delete mode 100644 src/core/base/hydrogendevapi.cpp delete mode 100644 src/core/base/hydrogendevapi.hpp delete mode 100644 src/core/base/hydrogendevapis.cpp delete mode 100644 src/core/base/hydrogendevapis.hpp delete mode 100644 src/core/base/hydrogenlilxml.hpp delete mode 100644 src/core/base/hydrogenstandardproperty.cpp delete mode 100644 src/core/base/hydrogenstandardproperty.h delete mode 100644 src/core/base/hydrogenuserio.cpp delete mode 100644 src/core/base/hydrogenuserio.hpp delete mode 100644 src/core/base/hydrogenutility.cpp delete mode 100644 src/core/base/hydrogenutility.hpp delete mode 100644 src/core/base/libastro.cpp delete mode 100644 src/core/base/libastro.hpp delete mode 100644 src/core/base/lilxml.cpp delete mode 100644 src/core/base/lilxml.hpp delete mode 100644 src/core/base/parentdevice.cpp delete mode 100644 src/core/base/parentdevice.h delete mode 100644 src/core/base/parentdevice_p.h delete mode 100644 src/core/base/sharedblob.cpp delete mode 100644 src/core/base/sharedblob.hpp delete mode 100644 src/core/base/sharedblob_parse.cpp delete mode 100644 src/core/base/sharedblob_parse.hpp delete mode 100644 src/core/base/shm_open_anon.cpp delete mode 100644 src/core/base/shm_open_anon.hpp delete mode 100644 src/core/base/userio.cpp delete mode 100644 src/core/base/userio.hpp delete mode 100644 src/core/base/watchdeviceproperty.cpp delete mode 100644 src/core/base/watchdeviceproperty.h delete mode 100644 src/core/locale/locale_compat.hpp delete mode 100644 src/core/property/hydrogenproperties.cpp delete mode 100644 src/core/property/hydrogenproperties.h delete mode 100644 src/core/property/hydrogenproperties_p.h delete mode 100644 src/core/property/hydrogenproperty.cpp delete mode 100644 src/core/property/hydrogenproperty.h delete mode 100644 src/core/property/hydrogenproperty_p.h delete mode 100644 src/core/property/hydrogenpropertybasic.cpp delete mode 100644 src/core/property/hydrogenpropertybasic.h delete mode 100644 src/core/property/hydrogenpropertybasic_p.h delete mode 100644 src/core/property/hydrogenpropertyblob.cpp delete mode 100644 src/core/property/hydrogenpropertyblob.h delete mode 100644 src/core/property/hydrogenpropertyblob_p.h delete mode 100644 src/core/property/hydrogenpropertylight.cpp delete mode 100644 src/core/property/hydrogenpropertylight.h delete mode 100644 src/core/property/hydrogenpropertylight_p.h delete mode 100644 src/core/property/hydrogenpropertynumber.cpp delete mode 100644 src/core/property/hydrogenpropertynumber.h delete mode 100644 src/core/property/hydrogenpropertynumber_p.h delete mode 100644 src/core/property/hydrogenpropertyswitch.cpp delete mode 100644 src/core/property/hydrogenpropertyswitch.h delete mode 100644 src/core/property/hydrogenpropertyswitch_p.h delete mode 100644 src/core/property/hydrogenpropertytext.cpp delete mode 100644 src/core/property/hydrogenpropertytext.h delete mode 100644 src/core/property/hydrogenpropertytext_p.h delete mode 100644 src/core/property/hydrogenpropertyview.cpp delete mode 100644 src/core/property/hydrogenpropertyview.h delete mode 100644 src/core/property/hydrogenwidgettraits.h delete mode 100644 src/core/property/hydrogenwidgetview.h diff --git a/libs b/libs index ea669667..b0a261c6 160000 --- a/libs +++ b/libs @@ -1 +1 @@ -Subproject commit ea66966758399823a75c02308a2fd7bb602c6610 +Subproject commit b0a261c6641c6fc114e1a176e5c39055b85346ef diff --git a/modules b/modules index 0ae8c516..d7f7153d 160000 --- a/modules +++ b/modules @@ -1 +1 @@ -Subproject commit 0ae8c516c999059cad17a6bb5704d9fbda50fea2 +Subproject commit d7f7153d0ad1b699707088dae345162d008d9253 diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c70548c6..d09b8084 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -3,7 +3,6 @@ project(lithiumcore C CXX) find_package(Threads REQUIRED) -add_subdirectory(base) add_subdirectory(plugin) include_directories(.) @@ -42,37 +41,12 @@ list(APPEND ${PROJECT_NAME}_SOURCES property/task/device_task.cpp property/task/plugin_task.cpp property/task/task.cpp - - property/hydrogenproperties.cpp - property/hydrogenproperty.cpp - property/hydrogenpropertybasic.cpp - property/hydrogenpropertyview.cpp - property/hydrogenpropertytext.cpp - property/hydrogenpropertynumber.cpp - property/hydrogenpropertyswitch.cpp - property/hydrogenpropertylight.cpp - property/hydrogenpropertyblob.cpp - - # connection/ttybase.cpp ) # Headers list(APPEND ${PROJECT_NAME}_HEADERS fitskeyword.h - property/hydrogenproperties.h - property/hydrogenproperty.h - property/hydrogenpropertybasic.h - property/hydrogenpropertyview.h - property/hydrogenpropertytext.h - property/hydrogenpropertynumber.h - property/hydrogenpropertyswitch.h - property/hydrogenpropertylight.h - property/hydrogenpropertyblob.h - - property/hydrogenwidgetview.h - property/hydrogenwidgettraits.h - device.hpp deviceio.hpp camera.hpp @@ -100,14 +74,6 @@ list(APPEND ${PROJECT_NAME}_HEADERS # Private Headers list(APPEND ${PROJECT_NAME}_PRIVATE_HEADERS - property/hydrogenproperty_p.h - property/hydrogenproperties_p.h - property/hydrogenpropertyblob_p.h - property/hydrogenpropertyswitch_p.h - property/hydrogenpropertylight_p.h - property/hydrogenpropertytext_p.h - property/hydrogenpropertynumber_p.h - property/hydrogenpropertybasic_p.h # TODO ) diff --git a/src/core/base/CMakeLists.txt b/src/core/base/CMakeLists.txt deleted file mode 100644 index 95cc71f7..00000000 --- a/src/core/base/CMakeLists.txt +++ /dev/null @@ -1,113 +0,0 @@ -cmake_minimum_required(VERSION 3.13) -project(hydrogencore C CXX) - -find_package(Nova) -add_library(${PROJECT_NAME} OBJECT "") - -include(CPack) - -set(CMAKE_CXX_STANDARD 20) - -configure_file(hydrogenapi.h.in ${CMAKE_BINARY_DIR}/hydrogenapi.h @ONLY) - -# Headers -list(APPEND ${PROJECT_NAME}_HEADERS - ${CMAKE_BINARY_DIR}/hydrogenapi.h - hydrogendevapi.hpp - hydrogenutility.hpp - lilxml.hpp - base64.hpp - hydrogencom.hpp - sharedblob.hpp - - hydrogenbase.h - hydrogenbasetypes.h - - basedevice.h - parentdevice.h - - hydrogenstandardproperty.h -) - -list(APPEND ${PROJECT_NAME}_PRIVATE_HEADERS - base64_luts.hpp - hydrogenlilxml.hpp - hydrogenuserio.hpp - userio.cpp - - parentdevice_p.h - basedevice_p.h - - watchdeviceproperty.h -) - -# Sources -list(APPEND ${PROJECT_NAME}_SOURCES - hydrogenutility.cpp - base64.cpp - userio.cpp - hydrogencom.cpp - hydrogendevapi.cpp - lilxml.cpp - hydrogenuserio.cpp - sharedblob.cpp - - hydrogenbase.cpp - - parentdevice.cpp - basedevice.cpp - watchdeviceproperty.cpp - - hydrogenstandardproperty.cpp -) - -if(UNIX OR WIN32 OR LINUX) - list(APPEND ${PROJECT_NAME}_PRIVATE_HEADERS - sharedblob_parse.hpp - shm_open_anon.hpp) - list(APPEND ${PROJECT_NAME}_SOURCES - sharedblob_parse.cpp - shm_open_anon.cpp) -endif() - -target_compile_definitions(${PROJECT_NAME} - PUBLIC - $<$:HAVE_LIBNOVA> - $<$:HAVE_TIMESPEC_GET> - $<$:HAVE_CLOCK_GETTIME> -) -if(NOVA_FOUND) - target_link_libraries(${PROJECT_NAME} ${NOVA_LIBRARIES}) - target_include_directories(${PROJECT_NAME} PRIVATE ${NOVA_INCLUDE_DIR}) - - list(APPEND ${PROJECT_NAME}_HEADERS - libastro.hpp - ) - - list(APPEND ${PROJECT_NAME}_SOURCES - libastro.cpp - ) -endif() - -# Setup Target -target_sources(${PROJECT_NAME} - PUBLIC - ${${PROJECT_NAME}_HEADERS} - PRIVATE - ${${PROJECT_NAME}_SOURCES} - ${${PROJECT_NAME}_PRIVATE_HEADERS} -) - -target_include_directories(${PROJECT_NAME} - PUBLIC - . - ${CMAKE_CURRENT_BINARY_DIR}/../.. # config.h - ${CMAKE_CURRENT_BINARY_DIR} # hydrogenapi.h -) - -install(FILES - ${${PROJECT_NAME}_HEADERS} - DESTINATION - ${INCLUDE_INSTALL_DIR}/libhydrogen - COMPONENT Devel -) diff --git a/src/core/base/base64.cpp b/src/core/base/base64.cpp deleted file mode 100644 index d89f0e09..00000000 --- a/src/core/base/base64.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - * base64.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: Base64 - -**************************************************/ - -#include -#include -#include "base64.hpp" -#include "base64_luts.hpp" - -/* - * as byteswap.h is not available on macos, add macro here - * Swap bytes in 16-bit value. - */ -//#define bswap_16(x) __builtin_bswap16 (x); -#define bswap_16(x) ((uint16_t) ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8))) - -#include -#ifdef _WIN32 - constexpr bool IS_BIG_ENDIAN = false; -#else - constexpr bool IS_BIG_ENDIAN = (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__); - #include -#endif - -#define IS_LITTLE_ENDIAN (!IS_BIG_ENDIAN) - -size_t to64frombits_s(unsigned char* out, const unsigned char* in, int inlen, size_t outlen) { - size_t dlen = (((size_t)inlen + 2) / 3) * 4; /* 4/3, rounded up */ - - if (dlen > outlen) { - return 0; - } - - uint16_t *b64lut = (uint16_t *)base64lut; - uint16_t *wbuf = (uint16_t *)out; - - for (; inlen > 2; inlen -= 3) - { - uint32_t n = in[0] << 16 | in[1] << 8 | in[2]; - - wbuf[0] = b64lut[n >> 12]; - wbuf[1] = b64lut[n & 0x00000fff]; - - wbuf += 2; - in += 3; - } - - out = (unsigned char *)wbuf; - if (inlen > 0) - { - unsigned char fragment; - *out++ = base64digits[in[0] >> 2]; - fragment = (in[0] << 4) & 0x30; - if (inlen > 1) - fragment |= in[1] >> 4; - *out++ = base64digits[fragment]; - *out++ = (inlen < 2) ? '=' : base64digits[(in[1] << 2) & 0x3c]; - *out++ = '='; - } - *out = 0; // NULL terminate - return dlen; -} - -int from64tobits(char* out, const char* in) { - const char* cp = in; - while (*cp != '\0') - cp += 4; - return from64tobits_fast(out, in, cp - in); -} - -int from64tobits_fast(char* out, const char* in, int inlen) { - int outlen = 0; - uint8_t b1, b2, b3; - uint16_t s1, s2; - uint32_t n32; - int j; - int n = (inlen / 4) - 1; - uint16_t* inp = reinterpret_cast(const_cast(in)); - - for (j = 0; j < n; j++) { - if (in[0] == '\n') - in++; - inp = reinterpret_cast(const_cast(in)); - - if constexpr (IS_BIG_ENDIAN) { - inp[0] = __builtin_bswap16(inp[0]); - inp[1] = __builtin_bswap16(inp[1]); - } - - s1 = rbase64lut[inp[0]]; - s2 = rbase64lut[inp[1]]; - - n32 = s1; - n32 <<= 10; - n32 |= s2 >> 2; - - b3 = (n32 & 0x00ff); - n32 >>= 8; - b2 = (n32 & 0x00ff); - n32 >>= 8; - b1 = (n32 & 0x00ff); - - *out++ = b1; - *out++ = b2; - *out++ = b3; - - in += 4; - out += 3; - } - outlen = (inlen / 4 - 1) * 3; - if (in[0] == '\n') - in++; - inp = reinterpret_cast(const_cast(in)); - if constexpr (IS_BIG_ENDIAN) { - inp[0] = __builtin_bswap16(inp[0]); - inp[1] = __builtin_bswap16(inp[1]); - } - - s1 = rbase64lut[inp[0]]; - s2 = rbase64lut[inp[1]]; - - n32 = s1; - n32 <<= 10; - n32 |= s2 >> 2; - - b3 = (n32 & 0x00ff); - n32 >>= 8; - b2 = (n32 & 0x00ff); - n32 >>= 8; - b1 = (n32 & 0x00ff); - - *out++ = b1; - outlen++; - if ((inp[1] & 0x00FF) != 0x003D) { - *out++ = b2; - outlen++; - if ((inp[1] & 0xFF00) != 0x3D00) { - *out++ = b3; - outlen++; - } - } - return outlen; -} diff --git a/src/core/base/base64.hpp b/src/core/base/base64.hpp deleted file mode 100644 index 06765189..00000000 --- a/src/core/base/base64.hpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * base64.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: Base64 - -**************************************************/ - -#pragma once - -#include - -/** - * \brief 将字节数组转换为base64编码的字符串。 - * - * \param out 输出的base64编码字符串。输出缓冲区大小必须至少为 (4 * inlen / 3 + 4) 字节。 - * \param in 输入的二进制缓冲区。 - * \param inlen 要转换的字节数。 - * \param outlen 输出缓冲区的大小。 - * \return 成功返回0,失败返回-1。 - */ -size_t to64frombits_s(unsigned char *out, const unsigned char *in, int inlen, size_t outlen); - -/** - * \brief 将base64编码的字符串转换为字节数组。 - * - * \param out 输出的二进制缓冲区。输出缓冲区的大小必须至少为 (3 * size_of_in_buffer / 4) 字节。 - * \param in 输入的base64编码字符串。 - * \param inlen base64编码字符串的长度。 - * \return 成功返回0,失败返回-1。 - */ -int from64tobits(char *out, const char *in); - -/** - * \brief 快速将base64编码的字符串转换为字节数组。 - * - * \param out 输出的二进制缓冲区。输出缓冲区的大小必须至少为 (3 * size_of_in_buffer / 4) 字节。 - * \param in 输入的base64编码字符串。 - * \param inlen base64编码字符串的长度。 - * \return 成功返回0,失败返回-1。 - */ -int from64tobits_fast(char *out, const char *in, int inlen); diff --git a/src/core/base/base64_luts.hpp b/src/core/base/base64_luts.hpp deleted file mode 100644 index 53638b88..00000000 --- a/src/core/base/base64_luts.hpp +++ /dev/null @@ -1,4252 +0,0 @@ -#if 0 -INDI -Copyright (C) 2016 Rumen G. Bogdanovski - -This library is free software; -you can redistribute it and / or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; -either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; -without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. -#endif - -#pragma once - -#include - -static const char base64digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -static const char base64lut[] = "AAABACADAEAFAGAHAIAJAKALAMANAOAPAQARASATAUAVAWAXAYAZAaAbAcAdAeAfAgAhAiAjAkAlAmAnAoApAq" - "ArAsAtAuAvAwAxAyAzA0A1A2A3A4A5A6A7A8A9A+A/" - "BABBBCBDBEBFBGBHBIBJBKBLBMBNBOBPBQBRBSBTBUBVBWBXBYBZBaBbBcBdBeBfBgBhBiBjBkBlBmBnBoBpBq" - "BrBsBtBuBvBwBxByBzB0B1B2B3B4B5B6B7B8B9B+B/" - "CACBCCCDCECFCGCHCICJCKCLCMCNCOCPCQCRCSCTCUCVCWCXCYCZCaCbCcCdCeCfCgChCiCjCkClCmCnCoCpCq" - "CrCsCtCuCvCwCxCyCzC0C1C2C3C4C5C6C7C8C9C+C/" - "DADBDCDDDEDFDGDHDIDJDKDLDMDNDODPDQDRDSDTDUDVDWDXDYDZDaDbDcDdDeDfDgDhDiDjDkDlDmDnDoDpDq" - "DrDsDtDuDvDwDxDyDzD0D1D2D3D4D5D6D7D8D9D+D/" - "EAEBECEDEEEFEGEHEIEJEKELEMENEOEPEQERESETEUEVEWEXEYEZEaEbEcEdEeEfEgEhEiEjEkElEmEnEoEpEq" - "ErEsEtEuEvEwExEyEzE0E1E2E3E4E5E6E7E8E9E+E/" - "FAFBFCFDFEFFFGFHFIFJFKFLFMFNFOFPFQFRFSFTFUFVFWFXFYFZFaFbFcFdFeFfFgFhFiFjFkFlFmFnFoFpFq" - "FrFsFtFuFvFwFxFyFzF0F1F2F3F4F5F6F7F8F9F+F/" - "GAGBGCGDGEGFGGGHGIGJGKGLGMGNGOGPGQGRGSGTGUGVGWGXGYGZGaGbGcGdGeGfGgGhGiGjGkGlGmGnGoGpGq" - "GrGsGtGuGvGwGxGyGzG0G1G2G3G4G5G6G7G8G9G+G/" - "HAHBHCHDHEHFHGHHHIHJHKHLHMHNHOHPHQHRHSHTHUHVHWHXHYHZHaHbHcHdHeHfHgHhHiHjHkHlHmHnHoHpHq" - "HrHsHtHuHvHwHxHyHzH0H1H2H3H4H5H6H7H8H9H+H/" - "IAIBICIDIEIFIGIHIIIJIKILIMINIOIPIQIRISITIUIVIWIXIYIZIaIbIcIdIeIfIgIhIiIjIkIlImInIoIpIq" - "IrIsItIuIvIwIxIyIzI0I1I2I3I4I5I6I7I8I9I+I/" - "JAJBJCJDJEJFJGJHJIJJJKJLJMJNJOJPJQJRJSJTJUJVJWJXJYJZJaJbJcJdJeJfJgJhJiJjJkJlJmJnJoJpJq" - "JrJsJtJuJvJwJxJyJzJ0J1J2J3J4J5J6J7J8J9J+J/" - "KAKBKCKDKEKFKGKHKIKJKKKLKMKNKOKPKQKRKSKTKUKVKWKXKYKZKaKbKcKdKeKfKgKhKiKjKkKlKmKnKoKpKq" - "KrKsKtKuKvKwKxKyKzK0K1K2K3K4K5K6K7K8K9K+K/" - "LALBLCLDLELFLGLHLILJLKLLLMLNLOLPLQLRLSLTLULVLWLXLYLZLaLbLcLdLeLfLgLhLiLjLkLlLmLnLoLpLq" - "LrLsLtLuLvLwLxLyLzL0L1L2L3L4L5L6L7L8L9L+L/" - "MAMBMCMDMEMFMGMHMIMJMKMLMMMNMOMPMQMRMSMTMUMVMWMXMYMZMaMbMcMdMeMfMgMhMiMjMkMlMmMnMoMpMq" - "MrMsMtMuMvMwMxMyMzM0M1M2M3M4M5M6M7M8M9M+M/" - "NANBNCNDNENFNGNHNINJNKNLNMNNNONPNQNRNSNTNUNVNWNXNYNZNaNbNcNdNeNfNgNhNiNjNkNlNmNnNoNpNq" - "NrNsNtNuNvNwNxNyNzN0N1N2N3N4N5N6N7N8N9N+N/" - "OAOBOCODOEOFOGOHOIOJOKOLOMONOOOPOQOROSOTOUOVOWOXOYOZOaObOcOdOeOfOgOhOiOjOkOlOmOnOoOpOq" - "OrOsOtOuOvOwOxOyOzO0O1O2O3O4O5O6O7O8O9O+O/" - "PAPBPCPDPEPFPGPHPIPJPKPLPMPNPOPPPQPRPSPTPUPVPWPXPYPZPaPbPcPdPePfPgPhPiPjPkPlPmPnPoPpPq" - "PrPsPtPuPvPwPxPyPzP0P1P2P3P4P5P6P7P8P9P+P/" - "QAQBQCQDQEQFQGQHQIQJQKQLQMQNQOQPQQQRQSQTQUQVQWQXQYQZQaQbQcQdQeQfQgQhQiQjQkQlQmQnQoQpQq" - "QrQsQtQuQvQwQxQyQzQ0Q1Q2Q3Q4Q5Q6Q7Q8Q9Q+Q/" - "RARBRCRDRERFRGRHRIRJRKRLRMRNRORPRQRRRSRTRURVRWRXRYRZRaRbRcRdReRfRgRhRiRjRkRlRmRnRoRpRq" - "RrRsRtRuRvRwRxRyRzR0R1R2R3R4R5R6R7R8R9R+R/" - "SASBSCSDSESFSGSHSISJSKSLSMSNSOSPSQSRSSSTSUSVSWSXSYSZSaSbScSdSeSfSgShSiSjSkSlSmSnSoSpSq" - "SrSsStSuSvSwSxSySzS0S1S2S3S4S5S6S7S8S9S+S/" - "TATBTCTDTETFTGTHTITJTKTLTMTNTOTPTQTRTSTTTUTVTWTXTYTZTaTbTcTdTeTfTgThTiTjTkTlTmTnToTpTq" - "TrTsTtTuTvTwTxTyTzT0T1T2T3T4T5T6T7T8T9T+T/" - "UAUBUCUDUEUFUGUHUIUJUKULUMUNUOUPUQURUSUTUUUVUWUXUYUZUaUbUcUdUeUfUgUhUiUjUkUlUmUnUoUpUq" - "UrUsUtUuUvUwUxUyUzU0U1U2U3U4U5U6U7U8U9U+U/" - "VAVBVCVDVEVFVGVHVIVJVKVLVMVNVOVPVQVRVSVTVUVVVWVXVYVZVaVbVcVdVeVfVgVhViVjVkVlVmVnVoVpVq" - "VrVsVtVuVvVwVxVyVzV0V1V2V3V4V5V6V7V8V9V+V/" - "WAWBWCWDWEWFWGWHWIWJWKWLWMWNWOWPWQWRWSWTWUWVWWWXWYWZWaWbWcWdWeWfWgWhWiWjWkWlWmWnWoWpWq" - "WrWsWtWuWvWwWxWyWzW0W1W2W3W4W5W6W7W8W9W+W/" - "XAXBXCXDXEXFXGXHXIXJXKXLXMXNXOXPXQXRXSXTXUXVXWXXXYXZXaXbXcXdXeXfXgXhXiXjXkXlXmXnXoXpXq" - "XrXsXtXuXvXwXxXyXzX0X1X2X3X4X5X6X7X8X9X+X/" - "YAYBYCYDYEYFYGYHYIYJYKYLYMYNYOYPYQYRYSYTYUYVYWYXYYYZYaYbYcYdYeYfYgYhYiYjYkYlYmYnYoYpYq" - "YrYsYtYuYvYwYxYyYzY0Y1Y2Y3Y4Y5Y6Y7Y8Y9Y+Y/" - "ZAZBZCZDZEZFZGZHZIZJZKZLZMZNZOZPZQZRZSZTZUZVZWZXZYZZZaZbZcZdZeZfZgZhZiZjZkZlZmZnZoZpZq" - "ZrZsZtZuZvZwZxZyZzZ0Z1Z2Z3Z4Z5Z6Z7Z8Z9Z+Z/" - "aAaBaCaDaEaFaGaHaIaJaKaLaMaNaOaPaQaRaSaTaUaVaWaXaYaZaaabacadaeafagahaiajakalamanaoapaq" - "arasatauavawaxayaza0a1a2a3a4a5a6a7a8a9a+a/" - "bAbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZbabbbcbdbebfbgbhbibjbkblbmbnbobpbq" - "brbsbtbubvbwbxbybzb0b1b2b3b4b5b6b7b8b9b+b/" - "cAcBcCcDcEcFcGcHcIcJcKcLcMcNcOcPcQcRcScTcUcVcWcXcYcZcacbcccdcecfcgchcicjckclcmcncocpcq" - "crcsctcucvcwcxcyczc0c1c2c3c4c5c6c7c8c9c+c/" - "dAdBdCdDdEdFdGdHdIdJdKdLdMdNdOdPdQdRdSdTdUdVdWdXdYdZdadbdcdddedfdgdhdidjdkdldmdndodpdq" - "drdsdtdudvdwdxdydzd0d1d2d3d4d5d6d7d8d9d+d/" - "eAeBeCeDeEeFeGeHeIeJeKeLeMeNeOePeQeReSeTeUeVeWeXeYeZeaebecedeeefegeheiejekelemeneoepeq" - "ereseteuevewexeyeze0e1e2e3e4e5e6e7e8e9e+e/" - "fAfBfCfDfEfFfGfHfIfJfKfLfMfNfOfPfQfRfSfTfUfVfWfXfYfZfafbfcfdfefffgfhfifjfkflfmfnfofpfq" - "frfsftfufvfwfxfyfzf0f1f2f3f4f5f6f7f8f9f+f/" - "gAgBgCgDgEgFgGgHgIgJgKgLgMgNgOgPgQgRgSgTgUgVgWgXgYgZgagbgcgdgegfggghgigjgkglgmgngogpgq" - "grgsgtgugvgwgxgygzg0g1g2g3g4g5g6g7g8g9g+g/" - "hAhBhChDhEhFhGhHhIhJhKhLhMhNhOhPhQhRhShThUhVhWhXhYhZhahbhchdhehfhghhhihjhkhlhmhnhohphq" - "hrhshthuhvhwhxhyhzh0h1h2h3h4h5h6h7h8h9h+h/" - "iAiBiCiDiEiFiGiHiIiJiKiLiMiNiOiPiQiRiSiTiUiViWiXiYiZiaibicidieifigihiiijikiliminioipiq" - "irisitiuiviwixiyizi0i1i2i3i4i5i6i7i8i9i+i/" - "jAjBjCjDjEjFjGjHjIjJjKjLjMjNjOjPjQjRjSjTjUjVjWjXjYjZjajbjcjdjejfjgjhjijjjkjljmjnjojpjq" - "jrjsjtjujvjwjxjyjzj0j1j2j3j4j5j6j7j8j9j+j/" - "kAkBkCkDkEkFkGkHkIkJkKkLkMkNkOkPkQkRkSkTkUkVkWkXkYkZkakbkckdkekfkgkhkikjkkklkmknkokpkq" - "krksktkukvkwkxkykzk0k1k2k3k4k5k6k7k8k9k+k/" - "lAlBlClDlElFlGlHlIlJlKlLlMlNlOlPlQlRlSlTlUlVlWlXlYlZlalblcldlelflglhliljlklllmlnlolplq" - "lrlsltlulvlwlxlylzl0l1l2l3l4l5l6l7l8l9l+l/" - "mAmBmCmDmEmFmGmHmImJmKmLmMmNmOmPmQmRmSmTmUmVmWmXmYmZmambmcmdmemfmgmhmimjmkmlmmmnmompmq" - "mrmsmtmumvmwmxmymzm0m1m2m3m4m5m6m7m8m9m+m/" - "nAnBnCnDnEnFnGnHnInJnKnLnMnNnOnPnQnRnSnTnUnVnWnXnYnZnanbncndnenfngnhninjnknlnmnnnonpnq" - "nrnsntnunvnwnxnynzn0n1n2n3n4n5n6n7n8n9n+n/" - "oAoBoCoDoEoFoGoHoIoJoKoLoMoNoOoPoQoRoSoToUoVoWoXoYoZoaobocodoeofogohoiojokolomonooopoq" - "orosotouovowoxoyozo0o1o2o3o4o5o6o7o8o9o+o/" - "pApBpCpDpEpFpGpHpIpJpKpLpMpNpOpPpQpRpSpTpUpVpWpXpYpZpapbpcpdpepfpgphpipjpkplpmpnpopppq" - "prpsptpupvpwpxpypzp0p1p2p3p4p5p6p7p8p9p+p/" - "qAqBqCqDqEqFqGqHqIqJqKqLqMqNqOqPqQqRqSqTqUqVqWqXqYqZqaqbqcqdqeqfqgqhqiqjqkqlqmqnqoqpqq" - "qrqsqtquqvqwqxqyqzq0q1q2q3q4q5q6q7q8q9q+q/" - "rArBrCrDrErFrGrHrIrJrKrLrMrNrOrPrQrRrSrTrUrVrWrXrYrZrarbrcrdrerfrgrhrirjrkrlrmrnrorprq" - "rrrsrtrurvrwrxryrzr0r1r2r3r4r5r6r7r8r9r+r/" - "sAsBsCsDsEsFsGsHsIsJsKsLsMsNsOsPsQsRsSsTsUsVsWsXsYsZsasbscsdsesfsgshsisjskslsmsnsospsq" - "srssstsusvswsxsyszs0s1s2s3s4s5s6s7s8s9s+s/" - "tAtBtCtDtEtFtGtHtItJtKtLtMtNtOtPtQtRtStTtUtVtWtXtYtZtatbtctdtetftgthtitjtktltmtntotptq" - "trtstttutvtwtxtytzt0t1t2t3t4t5t6t7t8t9t+t/" - "uAuBuCuDuEuFuGuHuIuJuKuLuMuNuOuPuQuRuSuTuUuVuWuXuYuZuaubucudueufuguhuiujukulumunuoupuq" - "urusutuuuvuwuxuyuzu0u1u2u3u4u5u6u7u8u9u+u/" - "vAvBvCvDvEvFvGvHvIvJvKvLvMvNvOvPvQvRvSvTvUvVvWvXvYvZvavbvcvdvevfvgvhvivjvkvlvmvnvovpvq" - "vrvsvtvuvvvwvxvyvzv0v1v2v3v4v5v6v7v8v9v+v/" - "wAwBwCwDwEwFwGwHwIwJwKwLwMwNwOwPwQwRwSwTwUwVwWwXwYwZwawbwcwdwewfwgwhwiwjwkwlwmwnwowpwq" - "wrwswtwuwvwwwxwywzw0w1w2w3w4w5w6w7w8w9w+w/" - "xAxBxCxDxExFxGxHxIxJxKxLxMxNxOxPxQxRxSxTxUxVxWxXxYxZxaxbxcxdxexfxgxhxixjxkxlxmxnxoxpxq" - "xrxsxtxuxvxwxxxyxzx0x1x2x3x4x5x6x7x8x9x+x/" - "yAyByCyDyEyFyGyHyIyJyKyLyMyNyOyPyQyRySyTyUyVyWyXyYyZyaybycydyeyfygyhyiyjykylymynyoypyq" - "yrysytyuyvywyxyyyzy0y1y2y3y4y5y6y7y8y9y+y/" - "zAzBzCzDzEzFzGzHzIzJzKzLzMzNzOzPzQzRzSzTzUzVzWzXzYzZzazbzczdzezfzgzhzizjzkzlzmznzozpzq" - "zrzsztzuzvzwzxzyzzz0z1z2z3z4z5z6z7z8z9z+z/" - "0A0B0C0D0E0F0G0H0I0J0K0L0M0N0O0P0Q0R0S0T0U0V0W0X0Y0Z0a0b0c0d0e0f0g0h0i0j0k0l0m0n0o0p0q" - "0r0s0t0u0v0w0x0y0z000102030405060708090+0/" - "1A1B1C1D1E1F1G1H1I1J1K1L1M1N1O1P1Q1R1S1T1U1V1W1X1Y1Z1a1b1c1d1e1f1g1h1i1j1k1l1m1n1o1p1q" - "1r1s1t1u1v1w1x1y1z101112131415161718191+1/" - "2A2B2C2D2E2F2G2H2I2J2K2L2M2N2O2P2Q2R2S2T2U2V2W2X2Y2Z2a2b2c2d2e2f2g2h2i2j2k2l2m2n2o2p2q" - "2r2s2t2u2v2w2x2y2z202122232425262728292+2/" - "3A3B3C3D3E3F3G3H3I3J3K3L3M3N3O3P3Q3R3S3T3U3V3W3X3Y3Z3a3b3c3d3e3f3g3h3i3j3k3l3m3n3o3p3q" - "3r3s3t3u3v3w3x3y3z303132333435363738393+3/" - "4A4B4C4D4E4F4G4H4I4J4K4L4M4N4O4P4Q4R4S4T4U4V4W4X4Y4Z4a4b4c4d4e4f4g4h4i4j4k4l4m4n4o4p4q" - "4r4s4t4u4v4w4x4y4z404142434445464748494+4/" - "5A5B5C5D5E5F5G5H5I5J5K5L5M5N5O5P5Q5R5S5T5U5V5W5X5Y5Z5a5b5c5d5e5f5g5h5i5j5k5l5m5n5o5p5q" - "5r5s5t5u5v5w5x5y5z505152535455565758595+5/" - "6A6B6C6D6E6F6G6H6I6J6K6L6M6N6O6P6Q6R6S6T6U6V6W6X6Y6Z6a6b6c6d6e6f6g6h6i6j6k6l6m6n6o6p6q" - "6r6s6t6u6v6w6x6y6z606162636465666768696+6/" - "7A7B7C7D7E7F7G7H7I7J7K7L7M7N7O7P7Q7R7S7T7U7V7W7X7Y7Z7a7b7c7d7e7f7g7h7i7j7k7l7m7n7o7p7q" - "7r7s7t7u7v7w7x7y7z707172737475767778797+7/" - "8A8B8C8D8E8F8G8H8I8J8K8L8M8N8O8P8Q8R8S8T8U8V8W8X8Y8Z8a8b8c8d8e8f8g8h8i8j8k8l8m8n8o8p8q" - "8r8s8t8u8v8w8x8y8z808182838485868788898+8/" - "9A9B9C9D9E9F9G9H9I9J9K9L9M9N9O9P9Q9R9S9T9U9V9W9X9Y9Z9a9b9c9d9e9f9g9h9i9j9k9l9m9n9o9p9q" - "9r9s9t9u9v9w9x9y9z909192939495969798999+9/" - "+A+B+C+D+E+F+G+H+I+J+K+L+M+N+O+P+Q+R+S+T+U+V+W+X+Y+Z+a+b+c+d+e+f+g+h+i+j+k+l+m+n+o+p+" - "q+r+s+t+u+v+w+x+y+z+0+1+2+3+4+5+6+7+8+9+++/" - "/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/" - "q/r/s/t/u/v/w/x/y/z/0/1/2/3/4/5/6/7/8/9/+//"; - -static const uint16_t rbase64lut[] = -{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, - 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, - 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 16120, 248, 248, 248, 16376, - 13560, 13816, 14072, 14328, 14584, 14840, 15096, 15352, 15608, 15864, 248, 248, 248, 25592, 248, 248, - 248, 248, 504, 760, 1016, 1272, 1528, 1784, 2040, 2296, 2552, 2808, 3064, 3320, 3576, 3832, - 4088, 4344, 4600, 4856, 5112, 5368, 5624, 5880, 6136, 6392, 6648, 248, 248, 248, 248, 248, - 248, 6904, 7160, 7416, 7672, 7928, 8184, 8440, 8696, 8952, 9208, 9464, 9720, 9976, 10232, 10488, - 10744, 11000, 11256, 11512, 11768, 12024, 12280, 12536, 12792, 13048, 13304, 248, 248, 248, 248, 248, - 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, - 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, - 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, - 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, - 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, - 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, - 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, - 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, - 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, - 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 16124, 252, 252, 252, 16380, - 13564, 13820, 14076, 14332, 14588, 14844, 15100, 15356, 15612, 15868, 252, 252, 252, 25596, 252, 252, - 252, 252, 508, 764, 1020, 1276, 1532, 1788, 2044, 2300, 2556, 2812, 3068, 3324, 3580, 3836, - 4092, 4348, 4604, 4860, 5116, 5372, 5628, 5884, 6140, 6396, 6652, 252, 252, 252, 252, 252, - 252, 6908, 7164, 7420, 7676, 7932, 8188, 8444, 8700, 8956, 9212, 9468, 9724, 9980, 10236, 10492, - 10748, 11004, 11260, 11516, 11772, 12028, 12284, 12540, 12796, 13052, 13308, 252, 252, 252, 252, 252, - 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, - 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, - 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, - 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, - 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, - 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, - 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, - 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, - 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 16080, 208, 208, 208, 16336, - 13520, 13776, 14032, 14288, 14544, 14800, 15056, 15312, 15568, 15824, 208, 208, 208, 25552, 208, 208, - 208, 208, 464, 720, 976, 1232, 1488, 1744, 2000, 2256, 2512, 2768, 3024, 3280, 3536, 3792, - 4048, 4304, 4560, 4816, 5072, 5328, 5584, 5840, 6096, 6352, 6608, 208, 208, 208, 208, 208, - 208, 6864, 7120, 7376, 7632, 7888, 8144, 8400, 8656, 8912, 9168, 9424, 9680, 9936, 10192, 10448, - 10704, 10960, 11216, 11472, 11728, 11984, 12240, 12496, 12752, 13008, 13264, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, - 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, - 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, - 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 16084, 212, 212, 212, 16340, - 13524, 13780, 14036, 14292, 14548, 14804, 15060, 15316, 15572, 15828, 212, 212, 212, 25556, 212, 212, - 212, 212, 468, 724, 980, 1236, 1492, 1748, 2004, 2260, 2516, 2772, 3028, 3284, 3540, 3796, - 4052, 4308, 4564, 4820, 5076, 5332, 5588, 5844, 6100, 6356, 6612, 212, 212, 212, 212, 212, - 212, 6868, 7124, 7380, 7636, 7892, 8148, 8404, 8660, 8916, 9172, 9428, 9684, 9940, 10196, 10452, - 10708, 10964, 11220, 11476, 11732, 11988, 12244, 12500, 12756, 13012, 13268, 212, 212, 212, 212, 212, - 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, - 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, - 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, - 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, - 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, - 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, - 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, - 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, - 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, - 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, - 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 16088, 216, 216, 216, 16344, - 13528, 13784, 14040, 14296, 14552, 14808, 15064, 15320, 15576, 15832, 216, 216, 216, 25560, 216, 216, - 216, 216, 472, 728, 984, 1240, 1496, 1752, 2008, 2264, 2520, 2776, 3032, 3288, 3544, 3800, - 4056, 4312, 4568, 4824, 5080, 5336, 5592, 5848, 6104, 6360, 6616, 216, 216, 216, 216, 216, - 216, 6872, 7128, 7384, 7640, 7896, 8152, 8408, 8664, 8920, 9176, 9432, 9688, 9944, 10200, 10456, - 10712, 10968, 11224, 11480, 11736, 11992, 12248, 12504, 12760, 13016, 13272, 216, 216, 216, 216, 216, - 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, - 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, - 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, - 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, - 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, - 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, - 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, - 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, - 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 16092, 220, 220, 220, 16348, - 13532, 13788, 14044, 14300, 14556, 14812, 15068, 15324, 15580, 15836, 220, 220, 220, 25564, 220, 220, - 220, 220, 476, 732, 988, 1244, 1500, 1756, 2012, 2268, 2524, 2780, 3036, 3292, 3548, 3804, - 4060, 4316, 4572, 4828, 5084, 5340, 5596, 5852, 6108, 6364, 6620, 220, 220, 220, 220, 220, - 220, 6876, 7132, 7388, 7644, 7900, 8156, 8412, 8668, 8924, 9180, 9436, 9692, 9948, 10204, 10460, - 10716, 10972, 11228, 11484, 11740, 11996, 12252, 12508, 12764, 13020, 13276, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, - 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, - 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 16096, 224, 224, 224, 16352, - 13536, 13792, 14048, 14304, 14560, 14816, 15072, 15328, 15584, 15840, 224, 224, 224, 25568, 224, 224, - 224, 224, 480, 736, 992, 1248, 1504, 1760, 2016, 2272, 2528, 2784, 3040, 3296, 3552, 3808, - 4064, 4320, 4576, 4832, 5088, 5344, 5600, 5856, 6112, 6368, 6624, 224, 224, 224, 224, 224, - 224, 6880, 7136, 7392, 7648, 7904, 8160, 8416, 8672, 8928, 9184, 9440, 9696, 9952, 10208, 10464, - 10720, 10976, 11232, 11488, 11744, 12000, 12256, 12512, 12768, 13024, 13280, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, - 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, - 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, - 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 16100, 228, 228, 228, 16356, - 13540, 13796, 14052, 14308, 14564, 14820, 15076, 15332, 15588, 15844, 228, 228, 228, 25572, 228, 228, - 228, 228, 484, 740, 996, 1252, 1508, 1764, 2020, 2276, 2532, 2788, 3044, 3300, 3556, 3812, - 4068, 4324, 4580, 4836, 5092, 5348, 5604, 5860, 6116, 6372, 6628, 228, 228, 228, 228, 228, - 228, 6884, 7140, 7396, 7652, 7908, 8164, 8420, 8676, 8932, 9188, 9444, 9700, 9956, 10212, 10468, - 10724, 10980, 11236, 11492, 11748, 12004, 12260, 12516, 12772, 13028, 13284, 228, 228, 228, 228, 228, - 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, - 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, - 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, - 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, - 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, - 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, - 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, - 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, - 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, - 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, - 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 16104, 232, 232, 232, 16360, - 13544, 13800, 14056, 14312, 14568, 14824, 15080, 15336, 15592, 15848, 232, 232, 232, 25576, 232, 232, - 232, 232, 488, 744, 1000, 1256, 1512, 1768, 2024, 2280, 2536, 2792, 3048, 3304, 3560, 3816, - 4072, 4328, 4584, 4840, 5096, 5352, 5608, 5864, 6120, 6376, 6632, 232, 232, 232, 232, 232, - 232, 6888, 7144, 7400, 7656, 7912, 8168, 8424, 8680, 8936, 9192, 9448, 9704, 9960, 10216, 10472, - 10728, 10984, 11240, 11496, 11752, 12008, 12264, 12520, 12776, 13032, 13288, 232, 232, 232, 232, 232, - 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, - 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, - 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, - 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, - 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, - 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, - 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, - 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, - 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, - 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, - 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 16108, 236, 236, 236, 16364, - 13548, 13804, 14060, 14316, 14572, 14828, 15084, 15340, 15596, 15852, 236, 236, 236, 25580, 236, 236, - 236, 236, 492, 748, 1004, 1260, 1516, 1772, 2028, 2284, 2540, 2796, 3052, 3308, 3564, 3820, - 4076, 4332, 4588, 4844, 5100, 5356, 5612, 5868, 6124, 6380, 6636, 236, 236, 236, 236, 236, - 236, 6892, 7148, 7404, 7660, 7916, 8172, 8428, 8684, 8940, 9196, 9452, 9708, 9964, 10220, 10476, - 10732, 10988, 11244, 11500, 11756, 12012, 12268, 12524, 12780, 13036, 13292, 236, 236, 236, 236, 236, - 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, - 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, - 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, - 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, - 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, - 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, - 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, - 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, - 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, - 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, - 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 16112, 240, 240, 240, 16368, - 13552, 13808, 14064, 14320, 14576, 14832, 15088, 15344, 15600, 15856, 240, 240, 240, 25584, 240, 240, - 240, 240, 496, 752, 1008, 1264, 1520, 1776, 2032, 2288, 2544, 2800, 3056, 3312, 3568, 3824, - 4080, 4336, 4592, 4848, 5104, 5360, 5616, 5872, 6128, 6384, 6640, 240, 240, 240, 240, 240, - 240, 6896, 7152, 7408, 7664, 7920, 8176, 8432, 8688, 8944, 9200, 9456, 9712, 9968, 10224, 10480, - 10736, 10992, 11248, 11504, 11760, 12016, 12272, 12528, 12784, 13040, 13296, 240, 240, 240, 240, 240, - 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, - 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, - 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, - 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, - 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, - 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, - 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, - 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, - 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, - 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, - 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 16116, 244, 244, 244, 16372, - 13556, 13812, 14068, 14324, 14580, 14836, 15092, 15348, 15604, 15860, 244, 244, 244, 25588, 244, 244, - 244, 244, 500, 756, 1012, 1268, 1524, 1780, 2036, 2292, 2548, 2804, 3060, 3316, 3572, 3828, - 4084, 4340, 4596, 4852, 5108, 5364, 5620, 5876, 6132, 6388, 6644, 244, 244, 244, 244, 244, - 244, 6900, 7156, 7412, 7668, 7924, 8180, 8436, 8692, 8948, 9204, 9460, 9716, 9972, 10228, 10484, - 10740, 10996, 11252, 11508, 11764, 12020, 12276, 12532, 12788, 13044, 13300, 244, 244, 244, 244, 244, - 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, - 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, - 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, - 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, - 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, - 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, - 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, - 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, - 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, - 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 16268, 396, 396, 396, 16268, - 13708, 13708, 14220, 14220, 14732, 14732, 15244, 15244, 15756, 15756, 396, 396, 396, 25484, 396, 396, - 396, 396, 396, 908, 908, 1420, 1420, 1932, 1932, 2444, 2444, 2956, 2956, 3468, 3468, 3980, - 3980, 4492, 4492, 5004, 5004, 5516, 5516, 6028, 6028, 6540, 6540, 396, 396, 396, 396, 396, - 396, 7052, 7052, 7564, 7564, 8076, 8076, 8588, 8588, 9100, 9100, 9612, 9612, 10124, 10124, 10636, - 10636, 11148, 11148, 11660, 11660, 12172, 12172, 12684, 12684, 13196, 13196, 396, 396, 396, 396, 396, - 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, - 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, - 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, - 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, - 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, - 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, - 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, - 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 15876, 4, 4, 4, 16132, - 13316, 13572, 13828, 14084, 14340, 14596, 14852, 15108, 15364, 15620, 4, 4, 4, 25348, 4, 4, - 4, 4, 260, 516, 772, 1028, 1284, 1540, 1796, 2052, 2308, 2564, 2820, 3076, 3332, 3588, - 3844, 4100, 4356, 4612, 4868, 5124, 5380, 5636, 5892, 6148, 6404, 4, 4, 4, 4, 4, - 4, 6660, 6916, 7172, 7428, 7684, 7940, 8196, 8452, 8708, 8964, 9220, 9476, 9732, 9988, 10244, - 10500, 10756, 11012, 11268, 11524, 11780, 12036, 12292, 12548, 12804, 13060, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 15880, 8, 8, 8, 16136, - 13320, 13576, 13832, 14088, 14344, 14600, 14856, 15112, 15368, 15624, 8, 8, 8, 25352, 8, 8, - 8, 8, 264, 520, 776, 1032, 1288, 1544, 1800, 2056, 2312, 2568, 2824, 3080, 3336, 3592, - 3848, 4104, 4360, 4616, 4872, 5128, 5384, 5640, 5896, 6152, 6408, 8, 8, 8, 8, 8, - 8, 6664, 6920, 7176, 7432, 7688, 7944, 8200, 8456, 8712, 8968, 9224, 9480, 9736, 9992, 10248, - 10504, 10760, 11016, 11272, 11528, 11784, 12040, 12296, 12552, 12808, 13064, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 15884, 12, 12, 12, 16140, - 13324, 13580, 13836, 14092, 14348, 14604, 14860, 15116, 15372, 15628, 12, 12, 12, 25356, 12, 12, - 12, 12, 268, 524, 780, 1036, 1292, 1548, 1804, 2060, 2316, 2572, 2828, 3084, 3340, 3596, - 3852, 4108, 4364, 4620, 4876, 5132, 5388, 5644, 5900, 6156, 6412, 12, 12, 12, 12, 12, - 12, 6668, 6924, 7180, 7436, 7692, 7948, 8204, 8460, 8716, 8972, 9228, 9484, 9740, 9996, 10252, - 10508, 10764, 11020, 11276, 11532, 11788, 12044, 12300, 12556, 12812, 13068, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 15888, 16, 16, 16, 16144, - 13328, 13584, 13840, 14096, 14352, 14608, 14864, 15120, 15376, 15632, 16, 16, 16, 25360, 16, 16, - 16, 16, 272, 528, 784, 1040, 1296, 1552, 1808, 2064, 2320, 2576, 2832, 3088, 3344, 3600, - 3856, 4112, 4368, 4624, 4880, 5136, 5392, 5648, 5904, 6160, 6416, 16, 16, 16, 16, 16, - 16, 6672, 6928, 7184, 7440, 7696, 7952, 8208, 8464, 8720, 8976, 9232, 9488, 9744, 10000, 10256, - 10512, 10768, 11024, 11280, 11536, 11792, 12048, 12304, 12560, 12816, 13072, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 15892, 20, 20, 20, 16148, - 13332, 13588, 13844, 14100, 14356, 14612, 14868, 15124, 15380, 15636, 20, 20, 20, 25364, 20, 20, - 20, 20, 276, 532, 788, 1044, 1300, 1556, 1812, 2068, 2324, 2580, 2836, 3092, 3348, 3604, - 3860, 4116, 4372, 4628, 4884, 5140, 5396, 5652, 5908, 6164, 6420, 20, 20, 20, 20, 20, - 20, 6676, 6932, 7188, 7444, 7700, 7956, 8212, 8468, 8724, 8980, 9236, 9492, 9748, 10004, 10260, - 10516, 10772, 11028, 11284, 11540, 11796, 12052, 12308, 12564, 12820, 13076, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 15896, 24, 24, 24, 16152, - 13336, 13592, 13848, 14104, 14360, 14616, 14872, 15128, 15384, 15640, 24, 24, 24, 25368, 24, 24, - 24, 24, 280, 536, 792, 1048, 1304, 1560, 1816, 2072, 2328, 2584, 2840, 3096, 3352, 3608, - 3864, 4120, 4376, 4632, 4888, 5144, 5400, 5656, 5912, 6168, 6424, 24, 24, 24, 24, 24, - 24, 6680, 6936, 7192, 7448, 7704, 7960, 8216, 8472, 8728, 8984, 9240, 9496, 9752, 10008, 10264, - 10520, 10776, 11032, 11288, 11544, 11800, 12056, 12312, 12568, 12824, 13080, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 15900, 28, 28, 28, 16156, - 13340, 13596, 13852, 14108, 14364, 14620, 14876, 15132, 15388, 15644, 28, 28, 28, 25372, 28, 28, - 28, 28, 284, 540, 796, 1052, 1308, 1564, 1820, 2076, 2332, 2588, 2844, 3100, 3356, 3612, - 3868, 4124, 4380, 4636, 4892, 5148, 5404, 5660, 5916, 6172, 6428, 28, 28, 28, 28, 28, - 28, 6684, 6940, 7196, 7452, 7708, 7964, 8220, 8476, 8732, 8988, 9244, 9500, 9756, 10012, 10268, - 10524, 10780, 11036, 11292, 11548, 11804, 12060, 12316, 12572, 12828, 13084, 28, 28, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 15904, 32, 32, 32, 16160, - 13344, 13600, 13856, 14112, 14368, 14624, 14880, 15136, 15392, 15648, 32, 32, 32, 25376, 32, 32, - 32, 32, 288, 544, 800, 1056, 1312, 1568, 1824, 2080, 2336, 2592, 2848, 3104, 3360, 3616, - 3872, 4128, 4384, 4640, 4896, 5152, 5408, 5664, 5920, 6176, 6432, 32, 32, 32, 32, 32, - 32, 6688, 6944, 7200, 7456, 7712, 7968, 8224, 8480, 8736, 8992, 9248, 9504, 9760, 10016, 10272, - 10528, 10784, 11040, 11296, 11552, 11808, 12064, 12320, 12576, 12832, 13088, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, - 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, - 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 15908, 36, 36, 36, 16164, - 13348, 13604, 13860, 14116, 14372, 14628, 14884, 15140, 15396, 15652, 36, 36, 36, 25380, 36, 36, - 36, 36, 292, 548, 804, 1060, 1316, 1572, 1828, 2084, 2340, 2596, 2852, 3108, 3364, 3620, - 3876, 4132, 4388, 4644, 4900, 5156, 5412, 5668, 5924, 6180, 6436, 36, 36, 36, 36, 36, - 36, 6692, 6948, 7204, 7460, 7716, 7972, 8228, 8484, 8740, 8996, 9252, 9508, 9764, 10020, 10276, - 10532, 10788, 11044, 11300, 11556, 11812, 12068, 12324, 12580, 12836, 13092, 36, 36, 36, 36, 36, - 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, - 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, - 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, - 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, - 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, - 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, - 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, - 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 15912, 40, 40, 40, 16168, - 13352, 13608, 13864, 14120, 14376, 14632, 14888, 15144, 15400, 15656, 40, 40, 40, 25384, 40, 40, - 40, 40, 296, 552, 808, 1064, 1320, 1576, 1832, 2088, 2344, 2600, 2856, 3112, 3368, 3624, - 3880, 4136, 4392, 4648, 4904, 5160, 5416, 5672, 5928, 6184, 6440, 40, 40, 40, 40, 40, - 40, 6696, 6952, 7208, 7464, 7720, 7976, 8232, 8488, 8744, 9000, 9256, 9512, 9768, 10024, 10280, - 10536, 10792, 11048, 11304, 11560, 11816, 12072, 12328, 12584, 12840, 13096, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 15916, 44, 44, 44, 16172, - 13356, 13612, 13868, 14124, 14380, 14636, 14892, 15148, 15404, 15660, 44, 44, 44, 25388, 44, 44, - 44, 44, 300, 556, 812, 1068, 1324, 1580, 1836, 2092, 2348, 2604, 2860, 3116, 3372, 3628, - 3884, 4140, 4396, 4652, 4908, 5164, 5420, 5676, 5932, 6188, 6444, 44, 44, 44, 44, 44, - 44, 6700, 6956, 7212, 7468, 7724, 7980, 8236, 8492, 8748, 9004, 9260, 9516, 9772, 10028, 10284, - 10540, 10796, 11052, 11308, 11564, 11820, 12076, 12332, 12588, 12844, 13100, 44, 44, 44, 44, 44, - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 15920, 48, 48, 48, 16176, - 13360, 13616, 13872, 14128, 14384, 14640, 14896, 15152, 15408, 15664, 48, 48, 48, 25392, 48, 48, - 48, 48, 304, 560, 816, 1072, 1328, 1584, 1840, 2096, 2352, 2608, 2864, 3120, 3376, 3632, - 3888, 4144, 4400, 4656, 4912, 5168, 5424, 5680, 5936, 6192, 6448, 48, 48, 48, 48, 48, - 48, 6704, 6960, 7216, 7472, 7728, 7984, 8240, 8496, 8752, 9008, 9264, 9520, 9776, 10032, 10288, - 10544, 10800, 11056, 11312, 11568, 11824, 12080, 12336, 12592, 12848, 13104, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, - 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, - 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 15924, 52, 52, 52, 16180, - 13364, 13620, 13876, 14132, 14388, 14644, 14900, 15156, 15412, 15668, 52, 52, 52, 25396, 52, 52, - 52, 52, 308, 564, 820, 1076, 1332, 1588, 1844, 2100, 2356, 2612, 2868, 3124, 3380, 3636, - 3892, 4148, 4404, 4660, 4916, 5172, 5428, 5684, 5940, 6196, 6452, 52, 52, 52, 52, 52, - 52, 6708, 6964, 7220, 7476, 7732, 7988, 8244, 8500, 8756, 9012, 9268, 9524, 9780, 10036, 10292, - 10548, 10804, 11060, 11316, 11572, 11828, 12084, 12340, 12596, 12852, 13108, 52, 52, 52, 52, 52, - 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, - 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, - 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, - 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, - 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, - 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, - 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, - 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, - 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, - 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, - 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 15928, 56, 56, 56, 16184, - 13368, 13624, 13880, 14136, 14392, 14648, 14904, 15160, 15416, 15672, 56, 56, 56, 25400, 56, 56, - 56, 56, 312, 568, 824, 1080, 1336, 1592, 1848, 2104, 2360, 2616, 2872, 3128, 3384, 3640, - 3896, 4152, 4408, 4664, 4920, 5176, 5432, 5688, 5944, 6200, 6456, 56, 56, 56, 56, 56, - 56, 6712, 6968, 7224, 7480, 7736, 7992, 8248, 8504, 8760, 9016, 9272, 9528, 9784, 10040, 10296, - 10552, 10808, 11064, 11320, 11576, 11832, 12088, 12344, 12600, 12856, 13112, 56, 56, 56, 56, 56, - 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, - 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, - 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, - 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, - 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, - 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, - 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, - 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, - 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, - 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, - 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 15932, 60, 60, 60, 16188, - 13372, 13628, 13884, 14140, 14396, 14652, 14908, 15164, 15420, 15676, 60, 60, 60, 25404, 60, 60, - 60, 60, 316, 572, 828, 1084, 1340, 1596, 1852, 2108, 2364, 2620, 2876, 3132, 3388, 3644, - 3900, 4156, 4412, 4668, 4924, 5180, 5436, 5692, 5948, 6204, 6460, 60, 60, 60, 60, 60, - 60, 6716, 6972, 7228, 7484, 7740, 7996, 8252, 8508, 8764, 9020, 9276, 9532, 9788, 10044, 10300, - 10556, 10812, 11068, 11324, 11580, 11836, 12092, 12348, 12604, 12860, 13116, 60, 60, 60, 60, 60, - 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, - 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, - 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, - 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, - 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, - 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, - 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, - 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 15936, 64, 64, 64, 16192, - 13376, 13632, 13888, 14144, 14400, 14656, 14912, 15168, 15424, 15680, 64, 64, 64, 25408, 64, 64, - 64, 64, 320, 576, 832, 1088, 1344, 1600, 1856, 2112, 2368, 2624, 2880, 3136, 3392, 3648, - 3904, 4160, 4416, 4672, 4928, 5184, 5440, 5696, 5952, 6208, 6464, 64, 64, 64, 64, 64, - 64, 6720, 6976, 7232, 7488, 7744, 8000, 8256, 8512, 8768, 9024, 9280, 9536, 9792, 10048, 10304, - 10560, 10816, 11072, 11328, 11584, 11840, 12096, 12352, 12608, 12864, 13120, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 15940, 68, 68, 68, 16196, - 13380, 13636, 13892, 14148, 14404, 14660, 14916, 15172, 15428, 15684, 68, 68, 68, 25412, 68, 68, - 68, 68, 324, 580, 836, 1092, 1348, 1604, 1860, 2116, 2372, 2628, 2884, 3140, 3396, 3652, - 3908, 4164, 4420, 4676, 4932, 5188, 5444, 5700, 5956, 6212, 6468, 68, 68, 68, 68, 68, - 68, 6724, 6980, 7236, 7492, 7748, 8004, 8260, 8516, 8772, 9028, 9284, 9540, 9796, 10052, 10308, - 10564, 10820, 11076, 11332, 11588, 11844, 12100, 12356, 12612, 12868, 13124, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, - 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, - 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 15944, 72, 72, 72, 16200, - 13384, 13640, 13896, 14152, 14408, 14664, 14920, 15176, 15432, 15688, 72, 72, 72, 25416, 72, 72, - 72, 72, 328, 584, 840, 1096, 1352, 1608, 1864, 2120, 2376, 2632, 2888, 3144, 3400, 3656, - 3912, 4168, 4424, 4680, 4936, 5192, 5448, 5704, 5960, 6216, 6472, 72, 72, 72, 72, 72, - 72, 6728, 6984, 7240, 7496, 7752, 8008, 8264, 8520, 8776, 9032, 9288, 9544, 9800, 10056, 10312, - 10568, 10824, 11080, 11336, 11592, 11848, 12104, 12360, 12616, 12872, 13128, 72, 72, 72, 72, 72, - 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, - 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, - 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, - 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, - 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, - 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, - 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, - 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 15948, 76, 76, 76, 16204, - 13388, 13644, 13900, 14156, 14412, 14668, 14924, 15180, 15436, 15692, 76, 76, 76, 25420, 76, 76, - 76, 76, 332, 588, 844, 1100, 1356, 1612, 1868, 2124, 2380, 2636, 2892, 3148, 3404, 3660, - 3916, 4172, 4428, 4684, 4940, 5196, 5452, 5708, 5964, 6220, 6476, 76, 76, 76, 76, 76, - 76, 6732, 6988, 7244, 7500, 7756, 8012, 8268, 8524, 8780, 9036, 9292, 9548, 9804, 10060, 10316, - 10572, 10828, 11084, 11340, 11596, 11852, 12108, 12364, 12620, 12876, 13132, 76, 76, 76, 76, 76, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 15952, 80, 80, 80, 16208, - 13392, 13648, 13904, 14160, 14416, 14672, 14928, 15184, 15440, 15696, 80, 80, 80, 25424, 80, 80, - 80, 80, 336, 592, 848, 1104, 1360, 1616, 1872, 2128, 2384, 2640, 2896, 3152, 3408, 3664, - 3920, 4176, 4432, 4688, 4944, 5200, 5456, 5712, 5968, 6224, 6480, 80, 80, 80, 80, 80, - 80, 6736, 6992, 7248, 7504, 7760, 8016, 8272, 8528, 8784, 9040, 9296, 9552, 9808, 10064, 10320, - 10576, 10832, 11088, 11344, 11600, 11856, 12112, 12368, 12624, 12880, 13136, 80, 80, 80, 80, 80, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, - 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, - 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, - 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 15956, 84, 84, 84, 16212, - 13396, 13652, 13908, 14164, 14420, 14676, 14932, 15188, 15444, 15700, 84, 84, 84, 25428, 84, 84, - 84, 84, 340, 596, 852, 1108, 1364, 1620, 1876, 2132, 2388, 2644, 2900, 3156, 3412, 3668, - 3924, 4180, 4436, 4692, 4948, 5204, 5460, 5716, 5972, 6228, 6484, 84, 84, 84, 84, 84, - 84, 6740, 6996, 7252, 7508, 7764, 8020, 8276, 8532, 8788, 9044, 9300, 9556, 9812, 10068, 10324, - 10580, 10836, 11092, 11348, 11604, 11860, 12116, 12372, 12628, 12884, 13140, 84, 84, 84, 84, 84, - 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, - 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, - 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, - 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, - 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, - 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, - 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, - 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, - 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, - 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, - 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 15960, 88, 88, 88, 16216, - 13400, 13656, 13912, 14168, 14424, 14680, 14936, 15192, 15448, 15704, 88, 88, 88, 25432, 88, 88, - 88, 88, 344, 600, 856, 1112, 1368, 1624, 1880, 2136, 2392, 2648, 2904, 3160, 3416, 3672, - 3928, 4184, 4440, 4696, 4952, 5208, 5464, 5720, 5976, 6232, 6488, 88, 88, 88, 88, 88, - 88, 6744, 7000, 7256, 7512, 7768, 8024, 8280, 8536, 8792, 9048, 9304, 9560, 9816, 10072, 10328, - 10584, 10840, 11096, 11352, 11608, 11864, 12120, 12376, 12632, 12888, 13144, 88, 88, 88, 88, 88, - 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, - 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, - 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, - 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, - 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, - 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, - 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, - 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, - 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, - 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, - 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 15964, 92, 92, 92, 16220, - 13404, 13660, 13916, 14172, 14428, 14684, 14940, 15196, 15452, 15708, 92, 92, 92, 25436, 92, 92, - 92, 92, 348, 604, 860, 1116, 1372, 1628, 1884, 2140, 2396, 2652, 2908, 3164, 3420, 3676, - 3932, 4188, 4444, 4700, 4956, 5212, 5468, 5724, 5980, 6236, 6492, 92, 92, 92, 92, 92, - 92, 6748, 7004, 7260, 7516, 7772, 8028, 8284, 8540, 8796, 9052, 9308, 9564, 9820, 10076, 10332, - 10588, 10844, 11100, 11356, 11612, 11868, 12124, 12380, 12636, 12892, 13148, 92, 92, 92, 92, 92, - 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, - 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, - 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, - 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, - 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, - 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, - 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, - 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, - 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, - 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, - 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 15968, 96, 96, 96, 16224, - 13408, 13664, 13920, 14176, 14432, 14688, 14944, 15200, 15456, 15712, 96, 96, 96, 25440, 96, 96, - 96, 96, 352, 608, 864, 1120, 1376, 1632, 1888, 2144, 2400, 2656, 2912, 3168, 3424, 3680, - 3936, 4192, 4448, 4704, 4960, 5216, 5472, 5728, 5984, 6240, 6496, 96, 96, 96, 96, 96, - 96, 6752, 7008, 7264, 7520, 7776, 8032, 8288, 8544, 8800, 9056, 9312, 9568, 9824, 10080, 10336, - 10592, 10848, 11104, 11360, 11616, 11872, 12128, 12384, 12640, 12896, 13152, 96, 96, 96, 96, 96, - 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, - 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, - 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, - 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, - 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, - 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, - 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, - 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, - 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, - 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, - 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 15972, 100, 100, 100, 16228, - 13412, 13668, 13924, 14180, 14436, 14692, 14948, 15204, 15460, 15716, 100, 100, 100, 25444, 100, 100, - 100, 100, 356, 612, 868, 1124, 1380, 1636, 1892, 2148, 2404, 2660, 2916, 3172, 3428, 3684, - 3940, 4196, 4452, 4708, 4964, 5220, 5476, 5732, 5988, 6244, 6500, 100, 100, 100, 100, 100, - 100, 6756, 7012, 7268, 7524, 7780, 8036, 8292, 8548, 8804, 9060, 9316, 9572, 9828, 10084, 10340, - 10596, 10852, 11108, 11364, 11620, 11876, 12132, 12388, 12644, 12900, 13156, 100, 100, 100, 100, 100, - 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, - 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, - 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, - 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, - 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, - 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, - 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, - 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 15976, 104, 104, 104, 16232, - 13416, 13672, 13928, 14184, 14440, 14696, 14952, 15208, 15464, 15720, 104, 104, 104, 25448, 104, 104, - 104, 104, 360, 616, 872, 1128, 1384, 1640, 1896, 2152, 2408, 2664, 2920, 3176, 3432, 3688, - 3944, 4200, 4456, 4712, 4968, 5224, 5480, 5736, 5992, 6248, 6504, 104, 104, 104, 104, 104, - 104, 6760, 7016, 7272, 7528, 7784, 8040, 8296, 8552, 8808, 9064, 9320, 9576, 9832, 10088, 10344, - 10600, 10856, 11112, 11368, 11624, 11880, 12136, 12392, 12648, 12904, 13160, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, - 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, - 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 15980, 108, 108, 108, 16236, - 13420, 13676, 13932, 14188, 14444, 14700, 14956, 15212, 15468, 15724, 108, 108, 108, 25452, 108, 108, - 108, 108, 364, 620, 876, 1132, 1388, 1644, 1900, 2156, 2412, 2668, 2924, 3180, 3436, 3692, - 3948, 4204, 4460, 4716, 4972, 5228, 5484, 5740, 5996, 6252, 6508, 108, 108, 108, 108, 108, - 108, 6764, 7020, 7276, 7532, 7788, 8044, 8300, 8556, 8812, 9068, 9324, 9580, 9836, 10092, 10348, - 10604, 10860, 11116, 11372, 11628, 11884, 12140, 12396, 12652, 12908, 13164, 108, 108, 108, 108, 108, - 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, - 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, - 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, - 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, - 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, - 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, - 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, - 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, - 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, - 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, - 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 15984, 112, 112, 112, 16240, - 13424, 13680, 13936, 14192, 14448, 14704, 14960, 15216, 15472, 15728, 112, 112, 112, 25456, 112, 112, - 112, 112, 368, 624, 880, 1136, 1392, 1648, 1904, 2160, 2416, 2672, 2928, 3184, 3440, 3696, - 3952, 4208, 4464, 4720, 4976, 5232, 5488, 5744, 6000, 6256, 6512, 112, 112, 112, 112, 112, - 112, 6768, 7024, 7280, 7536, 7792, 8048, 8304, 8560, 8816, 9072, 9328, 9584, 9840, 10096, 10352, - 10608, 10864, 11120, 11376, 11632, 11888, 12144, 12400, 12656, 12912, 13168, 112, 112, 112, 112, 112, - 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, - 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, - 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, - 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, - 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, - 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, - 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, - 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 15988, 116, 116, 116, 16244, - 13428, 13684, 13940, 14196, 14452, 14708, 14964, 15220, 15476, 15732, 116, 116, 116, 25460, 116, 116, - 116, 116, 372, 628, 884, 1140, 1396, 1652, 1908, 2164, 2420, 2676, 2932, 3188, 3444, 3700, - 3956, 4212, 4468, 4724, 4980, 5236, 5492, 5748, 6004, 6260, 6516, 116, 116, 116, 116, 116, - 116, 6772, 7028, 7284, 7540, 7796, 8052, 8308, 8564, 8820, 9076, 9332, 9588, 9844, 10100, 10356, - 10612, 10868, 11124, 11380, 11636, 11892, 12148, 12404, 12660, 12916, 13172, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, - 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, - 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, - 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 15992, 120, 120, 120, 16248, - 13432, 13688, 13944, 14200, 14456, 14712, 14968, 15224, 15480, 15736, 120, 120, 120, 25464, 120, 120, - 120, 120, 376, 632, 888, 1144, 1400, 1656, 1912, 2168, 2424, 2680, 2936, 3192, 3448, 3704, - 3960, 4216, 4472, 4728, 4984, 5240, 5496, 5752, 6008, 6264, 6520, 120, 120, 120, 120, 120, - 120, 6776, 7032, 7288, 7544, 7800, 8056, 8312, 8568, 8824, 9080, 9336, 9592, 9848, 10104, 10360, - 10616, 10872, 11128, 11384, 11640, 11896, 12152, 12408, 12664, 12920, 13176, 120, 120, 120, 120, 120, - 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, - 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, - 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, - 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, - 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, - 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, - 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, - 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, - 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, - 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, - 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 15996, 124, 124, 124, 16252, - 13436, 13692, 13948, 14204, 14460, 14716, 14972, 15228, 15484, 15740, 124, 124, 124, 25468, 124, 124, - 124, 124, 380, 636, 892, 1148, 1404, 1660, 1916, 2172, 2428, 2684, 2940, 3196, 3452, 3708, - 3964, 4220, 4476, 4732, 4988, 5244, 5500, 5756, 6012, 6268, 6524, 124, 124, 124, 124, 124, - 124, 6780, 7036, 7292, 7548, 7804, 8060, 8316, 8572, 8828, 9084, 9340, 9596, 9852, 10108, 10364, - 10620, 10876, 11132, 11388, 11644, 11900, 12156, 12412, 12668, 12924, 13180, 124, 124, 124, 124, 124, - 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, - 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, - 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, - 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, - 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, - 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, - 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, - 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16000, 128, 128, 128, 16256, - 13440, 13696, 13952, 14208, 14464, 14720, 14976, 15232, 15488, 15744, 128, 128, 128, 25472, 128, 128, - 128, 128, 384, 640, 896, 1152, 1408, 1664, 1920, 2176, 2432, 2688, 2944, 3200, 3456, 3712, - 3968, 4224, 4480, 4736, 4992, 5248, 5504, 5760, 6016, 6272, 6528, 128, 128, 128, 128, 128, - 128, 6784, 7040, 7296, 7552, 7808, 8064, 8320, 8576, 8832, 9088, 9344, 9600, 9856, 10112, 10368, - 10624, 10880, 11136, 11392, 11648, 11904, 12160, 12416, 12672, 12928, 13184, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, - 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, - 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 16004, 132, 132, 132, 16260, - 13444, 13700, 13956, 14212, 14468, 14724, 14980, 15236, 15492, 15748, 132, 132, 132, 25476, 132, 132, - 132, 132, 388, 644, 900, 1156, 1412, 1668, 1924, 2180, 2436, 2692, 2948, 3204, 3460, 3716, - 3972, 4228, 4484, 4740, 4996, 5252, 5508, 5764, 6020, 6276, 6532, 132, 132, 132, 132, 132, - 132, 6788, 7044, 7300, 7556, 7812, 8068, 8324, 8580, 8836, 9092, 9348, 9604, 9860, 10116, 10372, - 10628, 10884, 11140, 11396, 11652, 11908, 12164, 12420, 12676, 12932, 13188, 132, 132, 132, 132, 132, - 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, - 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, - 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, - 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, - 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, - 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, - 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, - 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, - 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, - 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, - 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 16008, 136, 136, 136, 16264, - 13448, 13704, 13960, 14216, 14472, 14728, 14984, 15240, 15496, 15752, 136, 136, 136, 25480, 136, 136, - 136, 136, 392, 648, 904, 1160, 1416, 1672, 1928, 2184, 2440, 2696, 2952, 3208, 3464, 3720, - 3976, 4232, 4488, 4744, 5000, 5256, 5512, 5768, 6024, 6280, 6536, 136, 136, 136, 136, 136, - 136, 6792, 7048, 7304, 7560, 7816, 8072, 8328, 8584, 8840, 9096, 9352, 9608, 9864, 10120, 10376, - 10632, 10888, 11144, 11400, 11656, 11912, 12168, 12424, 12680, 12936, 13192, 136, 136, 136, 136, 136, - 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, - 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, - 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, - 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, - 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, - 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, - 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, - 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, - 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, - 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, - 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 16012, 140, 140, 140, 16268, - 13452, 13708, 13964, 14220, 14476, 14732, 14988, 15244, 15500, 15756, 140, 140, 140, 25484, 140, 140, - 140, 140, 396, 652, 908, 1164, 1420, 1676, 1932, 2188, 2444, 2700, 2956, 3212, 3468, 3724, - 3980, 4236, 4492, 4748, 5004, 5260, 5516, 5772, 6028, 6284, 6540, 140, 140, 140, 140, 140, - 140, 6796, 7052, 7308, 7564, 7820, 8076, 8332, 8588, 8844, 9100, 9356, 9612, 9868, 10124, 10380, - 10636, 10892, 11148, 11404, 11660, 11916, 12172, 12428, 12684, 12940, 13196, 140, 140, 140, 140, 140, - 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, - 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, - 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, - 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, - 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, - 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, - 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, - 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, - 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, - 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, - 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 16016, 144, 144, 144, 16272, - 13456, 13712, 13968, 14224, 14480, 14736, 14992, 15248, 15504, 15760, 144, 144, 144, 25488, 144, 144, - 144, 144, 400, 656, 912, 1168, 1424, 1680, 1936, 2192, 2448, 2704, 2960, 3216, 3472, 3728, - 3984, 4240, 4496, 4752, 5008, 5264, 5520, 5776, 6032, 6288, 6544, 144, 144, 144, 144, 144, - 144, 6800, 7056, 7312, 7568, 7824, 8080, 8336, 8592, 8848, 9104, 9360, 9616, 9872, 10128, 10384, - 10640, 10896, 11152, 11408, 11664, 11920, 12176, 12432, 12688, 12944, 13200, 144, 144, 144, 144, 144, - 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, - 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, - 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, - 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, - 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, - 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, - 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, - 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, - 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, - 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, - 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 16020, 148, 148, 148, 16276, - 13460, 13716, 13972, 14228, 14484, 14740, 14996, 15252, 15508, 15764, 148, 148, 148, 25492, 148, 148, - 148, 148, 404, 660, 916, 1172, 1428, 1684, 1940, 2196, 2452, 2708, 2964, 3220, 3476, 3732, - 3988, 4244, 4500, 4756, 5012, 5268, 5524, 5780, 6036, 6292, 6548, 148, 148, 148, 148, 148, - 148, 6804, 7060, 7316, 7572, 7828, 8084, 8340, 8596, 8852, 9108, 9364, 9620, 9876, 10132, 10388, - 10644, 10900, 11156, 11412, 11668, 11924, 12180, 12436, 12692, 12948, 13204, 148, 148, 148, 148, 148, - 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, - 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, - 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, - 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, - 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, - 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, - 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, - 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, - 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, - 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, - 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 16024, 152, 152, 152, 16280, - 13464, 13720, 13976, 14232, 14488, 14744, 15000, 15256, 15512, 15768, 152, 152, 152, 25496, 152, 152, - 152, 152, 408, 664, 920, 1176, 1432, 1688, 1944, 2200, 2456, 2712, 2968, 3224, 3480, 3736, - 3992, 4248, 4504, 4760, 5016, 5272, 5528, 5784, 6040, 6296, 6552, 152, 152, 152, 152, 152, - 152, 6808, 7064, 7320, 7576, 7832, 8088, 8344, 8600, 8856, 9112, 9368, 9624, 9880, 10136, 10392, - 10648, 10904, 11160, 11416, 11672, 11928, 12184, 12440, 12696, 12952, 13208, 152, 152, 152, 152, 152, - 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, - 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, - 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, - 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, - 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, - 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, - 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, - 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, - 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, - 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, - 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 16028, 156, 156, 156, 16284, - 13468, 13724, 13980, 14236, 14492, 14748, 15004, 15260, 15516, 15772, 156, 156, 156, 25500, 156, 156, - 156, 156, 412, 668, 924, 1180, 1436, 1692, 1948, 2204, 2460, 2716, 2972, 3228, 3484, 3740, - 3996, 4252, 4508, 4764, 5020, 5276, 5532, 5788, 6044, 6300, 6556, 156, 156, 156, 156, 156, - 156, 6812, 7068, 7324, 7580, 7836, 8092, 8348, 8604, 8860, 9116, 9372, 9628, 9884, 10140, 10396, - 10652, 10908, 11164, 11420, 11676, 11932, 12188, 12444, 12700, 12956, 13212, 156, 156, 156, 156, 156, - 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, - 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, - 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, - 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, - 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, - 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, - 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, - 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, - 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, - 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, - 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 16032, 160, 160, 160, 16288, - 13472, 13728, 13984, 14240, 14496, 14752, 15008, 15264, 15520, 15776, 160, 160, 160, 25504, 160, 160, - 160, 160, 416, 672, 928, 1184, 1440, 1696, 1952, 2208, 2464, 2720, 2976, 3232, 3488, 3744, - 4000, 4256, 4512, 4768, 5024, 5280, 5536, 5792, 6048, 6304, 6560, 160, 160, 160, 160, 160, - 160, 6816, 7072, 7328, 7584, 7840, 8096, 8352, 8608, 8864, 9120, 9376, 9632, 9888, 10144, 10400, - 10656, 10912, 11168, 11424, 11680, 11936, 12192, 12448, 12704, 12960, 13216, 160, 160, 160, 160, 160, - 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, - 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, - 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, - 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, - 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, - 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, - 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, - 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, - 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, - 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, - 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 16036, 164, 164, 164, 16292, - 13476, 13732, 13988, 14244, 14500, 14756, 15012, 15268, 15524, 15780, 164, 164, 164, 25508, 164, 164, - 164, 164, 420, 676, 932, 1188, 1444, 1700, 1956, 2212, 2468, 2724, 2980, 3236, 3492, 3748, - 4004, 4260, 4516, 4772, 5028, 5284, 5540, 5796, 6052, 6308, 6564, 164, 164, 164, 164, 164, - 164, 6820, 7076, 7332, 7588, 7844, 8100, 8356, 8612, 8868, 9124, 9380, 9636, 9892, 10148, 10404, - 10660, 10916, 11172, 11428, 11684, 11940, 12196, 12452, 12708, 12964, 13220, 164, 164, 164, 164, 164, - 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, - 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, - 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, - 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, - 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, - 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, - 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, - 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, - 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, - 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, - 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 16040, 168, 168, 168, 16296, - 13480, 13736, 13992, 14248, 14504, 14760, 15016, 15272, 15528, 15784, 168, 168, 168, 25512, 168, 168, - 168, 168, 424, 680, 936, 1192, 1448, 1704, 1960, 2216, 2472, 2728, 2984, 3240, 3496, 3752, - 4008, 4264, 4520, 4776, 5032, 5288, 5544, 5800, 6056, 6312, 6568, 168, 168, 168, 168, 168, - 168, 6824, 7080, 7336, 7592, 7848, 8104, 8360, 8616, 8872, 9128, 9384, 9640, 9896, 10152, 10408, - 10664, 10920, 11176, 11432, 11688, 11944, 12200, 12456, 12712, 12968, 13224, 168, 168, 168, 168, 168, - 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, - 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, - 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, - 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, - 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, - 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, - 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, - 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, - 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, - 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, - 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 16044, 172, 172, 172, 16300, - 13484, 13740, 13996, 14252, 14508, 14764, 15020, 15276, 15532, 15788, 172, 172, 172, 25516, 172, 172, - 172, 172, 428, 684, 940, 1196, 1452, 1708, 1964, 2220, 2476, 2732, 2988, 3244, 3500, 3756, - 4012, 4268, 4524, 4780, 5036, 5292, 5548, 5804, 6060, 6316, 6572, 172, 172, 172, 172, 172, - 172, 6828, 7084, 7340, 7596, 7852, 8108, 8364, 8620, 8876, 9132, 9388, 9644, 9900, 10156, 10412, - 10668, 10924, 11180, 11436, 11692, 11948, 12204, 12460, 12716, 12972, 13228, 172, 172, 172, 172, 172, - 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, - 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, - 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, - 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, - 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, - 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, - 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, - 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, - 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, - 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, - 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 16048, 176, 176, 176, 16304, - 13488, 13744, 14000, 14256, 14512, 14768, 15024, 15280, 15536, 15792, 176, 176, 176, 25520, 176, 176, - 176, 176, 432, 688, 944, 1200, 1456, 1712, 1968, 2224, 2480, 2736, 2992, 3248, 3504, 3760, - 4016, 4272, 4528, 4784, 5040, 5296, 5552, 5808, 6064, 6320, 6576, 176, 176, 176, 176, 176, - 176, 6832, 7088, 7344, 7600, 7856, 8112, 8368, 8624, 8880, 9136, 9392, 9648, 9904, 10160, 10416, - 10672, 10928, 11184, 11440, 11696, 11952, 12208, 12464, 12720, 12976, 13232, 176, 176, 176, 176, 176, - 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, - 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, - 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, - 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, - 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, - 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, - 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, - 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, - 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, - 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, - 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 16052, 180, 180, 180, 16308, - 13492, 13748, 14004, 14260, 14516, 14772, 15028, 15284, 15540, 15796, 180, 180, 180, 25524, 180, 180, - 180, 180, 436, 692, 948, 1204, 1460, 1716, 1972, 2228, 2484, 2740, 2996, 3252, 3508, 3764, - 4020, 4276, 4532, 4788, 5044, 5300, 5556, 5812, 6068, 6324, 6580, 180, 180, 180, 180, 180, - 180, 6836, 7092, 7348, 7604, 7860, 8116, 8372, 8628, 8884, 9140, 9396, 9652, 9908, 10164, 10420, - 10676, 10932, 11188, 11444, 11700, 11956, 12212, 12468, 12724, 12980, 13236, 180, 180, 180, 180, 180, - 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, - 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, - 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, - 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, - 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, - 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, - 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, - 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, - 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, - 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, - 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 16056, 184, 184, 184, 16312, - 13496, 13752, 14008, 14264, 14520, 14776, 15032, 15288, 15544, 15800, 184, 184, 184, 25528, 184, 184, - 184, 184, 440, 696, 952, 1208, 1464, 1720, 1976, 2232, 2488, 2744, 3000, 3256, 3512, 3768, - 4024, 4280, 4536, 4792, 5048, 5304, 5560, 5816, 6072, 6328, 6584, 184, 184, 184, 184, 184, - 184, 6840, 7096, 7352, 7608, 7864, 8120, 8376, 8632, 8888, 9144, 9400, 9656, 9912, 10168, 10424, - 10680, 10936, 11192, 11448, 11704, 11960, 12216, 12472, 12728, 12984, 13240, 184, 184, 184, 184, 184, - 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, - 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, - 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, - 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, - 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, - 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, - 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, - 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, - 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, - 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, - 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 16060, 188, 188, 188, 16316, - 13500, 13756, 14012, 14268, 14524, 14780, 15036, 15292, 15548, 15804, 188, 188, 188, 25532, 188, 188, - 188, 188, 444, 700, 956, 1212, 1468, 1724, 1980, 2236, 2492, 2748, 3004, 3260, 3516, 3772, - 4028, 4284, 4540, 4796, 5052, 5308, 5564, 5820, 6076, 6332, 6588, 188, 188, 188, 188, 188, - 188, 6844, 7100, 7356, 7612, 7868, 8124, 8380, 8636, 8892, 9148, 9404, 9660, 9916, 10172, 10428, - 10684, 10940, 11196, 11452, 11708, 11964, 12220, 12476, 12732, 12988, 13244, 188, 188, 188, 188, 188, - 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, - 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, - 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, - 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, - 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, - 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, - 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, - 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 16064, 192, 192, 192, 16320, - 13504, 13760, 14016, 14272, 14528, 14784, 15040, 15296, 15552, 15808, 192, 192, 192, 25536, 192, 192, - 192, 192, 448, 704, 960, 1216, 1472, 1728, 1984, 2240, 2496, 2752, 3008, 3264, 3520, 3776, - 4032, 4288, 4544, 4800, 5056, 5312, 5568, 5824, 6080, 6336, 6592, 192, 192, 192, 192, 192, - 192, 6848, 7104, 7360, 7616, 7872, 8128, 8384, 8640, 8896, 9152, 9408, 9664, 9920, 10176, 10432, - 10688, 10944, 11200, 11456, 11712, 11968, 12224, 12480, 12736, 12992, 13248, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, - 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, - 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 16068, 196, 196, 196, 16324, - 13508, 13764, 14020, 14276, 14532, 14788, 15044, 15300, 15556, 15812, 196, 196, 196, 25540, 196, 196, - 196, 196, 452, 708, 964, 1220, 1476, 1732, 1988, 2244, 2500, 2756, 3012, 3268, 3524, 3780, - 4036, 4292, 4548, 4804, 5060, 5316, 5572, 5828, 6084, 6340, 6596, 196, 196, 196, 196, 196, - 196, 6852, 7108, 7364, 7620, 7876, 8132, 8388, 8644, 8900, 9156, 9412, 9668, 9924, 10180, 10436, - 10692, 10948, 11204, 11460, 11716, 11972, 12228, 12484, 12740, 12996, 13252, 196, 196, 196, 196, 196, - 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, - 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, - 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, - 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, - 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, - 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, - 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, - 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, - 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, - 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, - 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 16072, 200, 200, 200, 16328, - 13512, 13768, 14024, 14280, 14536, 14792, 15048, 15304, 15560, 15816, 200, 200, 200, 25544, 200, 200, - 200, 200, 456, 712, 968, 1224, 1480, 1736, 1992, 2248, 2504, 2760, 3016, 3272, 3528, 3784, - 4040, 4296, 4552, 4808, 5064, 5320, 5576, 5832, 6088, 6344, 6600, 200, 200, 200, 200, 200, - 200, 6856, 7112, 7368, 7624, 7880, 8136, 8392, 8648, 8904, 9160, 9416, 9672, 9928, 10184, 10440, - 10696, 10952, 11208, 11464, 11720, 11976, 12232, 12488, 12744, 13000, 13256, 200, 200, 200, 200, 200, - 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, - 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, - 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, - 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, - 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, - 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, - 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, - 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16076, 204, 204, 204, 16332, - 13516, 13772, 14028, 14284, 14540, 14796, 15052, 15308, 15564, 15820, 204, 204, 204, 25548, 204, 204, - 204, 204, 460, 716, 972, 1228, 1484, 1740, 1996, 2252, 2508, 2764, 3020, 3276, 3532, 3788, - 4044, 4300, 4556, 4812, 5068, 5324, 5580, 5836, 6092, 6348, 6604, 204, 204, 204, 204, 204, - 204, 6860, 7116, 7372, 7628, 7884, 8140, 8396, 8652, 8908, 9164, 9420, 9676, 9932, 10188, 10444, - 10700, 10956, 11212, 11468, 11724, 11980, 12236, 12492, 12748, 13004, 13260, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15872, 0, 0, 0, 16128, - 13312, 13568, 13824, 14080, 14336, 14592, 14848, 15104, 15360, 15616, 0, 0, 0, 25344, 0, 0, - 0, 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, - 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 0, 0, 0, 0, 0, - 0, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, - 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; diff --git a/src/core/base/basedevice.cpp b/src/core/base/basedevice.cpp deleted file mode 100644 index 714cc202..00000000 --- a/src/core/base/basedevice.cpp +++ /dev/null @@ -1,993 +0,0 @@ -/******************************************************************************* - Copyright(c) 2011 Jasem Mutlaq. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#include "basedevice.h" -#include "basedevice_p.h" - -#include "base64.hpp" -#include "config.h" -#include "hydrogencom.hpp" -#include "hydrogenstandardproperty.h" -#include "locale/locale_compat.hpp" - -#include "hydrogenpropertytext.h" -#include "hydrogenpropertynumber.h" -#include "hydrogenpropertyswitch.h" -#include "hydrogenpropertylight.h" -#include "hydrogenpropertyblob.h" - -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY -#include "sharedblob_parse.hpp" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) -#define snprintf _snprintf -#pragma warning(push) -///@todo Introduce platform independent safe functions as macros to fix this -#pragma warning(disable : 4996) -#endif - -namespace HYDROGEN -{ - - BaseDevicePrivate::BaseDevicePrivate() - { - static char indidev[] = "INDIDEV="; - - if (getenv("INDIDEV") != nullptr) - { - deviceName = getenv("INDIDEV"); - putenv(indidev); - } - } - - BaseDevicePrivate::~BaseDevicePrivate() - { - pAll.clear(); - } - - BaseDevice::BaseDevice() - : d_ptr(BaseDevicePrivate::invalid()) - { - } - - BaseDevice::~BaseDevice() - { - } - - BaseDevice::BaseDevice(BaseDevicePrivate &dd) - : d_ptr(&dd) - { - } - - BaseDevice::BaseDevice(const std::shared_ptr &dd) - : d_ptr(dd) - { - } - - HYDROGEN::PropertyNumber BaseDevice::getNumber(const char *name) const - { - return getProperty(name, HYDROGEN_NUMBER); - } - - HYDROGEN::PropertyText BaseDevice::getText(const char *name) const - { - return getProperty(name, HYDROGEN_TEXT); - } - - HYDROGEN::PropertySwitch BaseDevice::getSwitch(const char *name) const - { - return getProperty(name, HYDROGEN_SWITCH); - } - - HYDROGEN::PropertyLight BaseDevice::getLight(const char *name) const - { - return getProperty(name, HYDROGEN_LIGHT); - } - - HYDROGEN::PropertyBlob BaseDevice::getBLOB(const char *name) const - { - return getProperty(name, HYDROGEN_BLOB); - } - - IPState BaseDevice::getPropertyState(const char *name) const - { - for (const auto &oneProp : getProperties()) - if (oneProp.isNameMatch(name)) - return oneProp.getState(); - - return IPS_IDLE; - } - - IPerm BaseDevice::getPropertyPermission(const char *name) const - { - for (const auto &oneProp : getProperties()) - if (oneProp.isNameMatch(name)) - return oneProp.getPermission(); - - return IP_RO; - } - - void *BaseDevice::getRawProperty(const char *name, HYDROGEN_PROPERTY_TYPE type) const - { - HYDROGEN::Property prop = getProperty(name, type); - return prop ? prop.getProperty() : nullptr; - } - - HYDROGEN::Property BaseDevice::getProperty(const char *name, HYDROGEN_PROPERTY_TYPE type) const - { - D_PTR(const BaseDevice); - std::lock_guard lock(d->m_Lock); - - for (const auto &oneProp : getProperties()) - { - if (type != oneProp.getType() && type != HYDROGEN_UNKNOWN) - continue; - - if (!oneProp.getRegistered()) - continue; - - if (oneProp.isNameMatch(name)) - return oneProp; - } - - return HYDROGEN::Property(); - } - - BaseDevice::Properties BaseDevice::getProperties() - { - D_PTR(BaseDevice); - return d->pAll; - } - - const BaseDevice::Properties BaseDevice::getProperties() const - { - D_PTR(const BaseDevice); - return d->pAll; - } - - int BaseDevice::removeProperty(const char *name, char *errmsg) - { - D_PTR(BaseDevice); - int result = HYDROGEN_PROPERTY_INVALID; - - std::lock_guard lock(d->m_Lock); - - d->pAll.erase_if([&name, &result](HYDROGEN::Property &prop) -> bool - { -#if 0 - if (prop.isNameMatch(name)) - { - // JM 2021-04-28: delete later. We perform the actual delete after 100ms to give clients a chance to remove the object. - // This is necessary when rapid define-delete-define sequences are made. - // This HACK is not ideal. We need to start using std::shared_ptr for this purpose soon, but this will be a major change to the - // interface. Perhaps for INDI 2.0 - std::thread([prop] - { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - }).detach(); - result = 0; - return true; - } - else - { - return false; - } -#endif - if (prop.isNameMatch(name)) - { - result = 0; - return true; - } - else - return false; }); - - if (result != 0) - snprintf(errmsg, MAXRBUF, "Error: Property %s not found in device %s.", name, getDeviceName()); - - return result; - } - - std::string BaseDevice::getSharedFilePath(std::string fileName) - { - std::string pathName; - - struct stat st; - - // absolute file path - if (stat(fileName.c_str(), &st) == 0) - { - pathName = fileName; - return pathName; - } - - // get base name of file - const size_t lastSlashIdx = fileName.find_last_of("\\/"); - if (std::string::npos != lastSlashIdx) - { - fileName.erase(0, lastSlashIdx + 1); - } - - const char *indiprefix = getenv("INDIPREFIX"); - if (indiprefix) - { -#if defined(OSX_EMBEDED_MODE) - pathName = std::string(indiprefix) + "/Contents/Resources/" + fileName; -#elif defined(__APPLE__) - pathName = std::string(indiprefix) + "/Contents/Resources/DriverSupport/" + fileName; -#else - pathName = std::string(indiprefix) + "/share/indi/" + fileName; -#endif - } - else - { - // TODO : pathName = std::string(DATA_INSTALL_DIR) + "/" + fileName; - } - return pathName; - } - - static std::string sGetSheletonFilePath(std::string fileName) - { - std::string pathName; - - struct stat st; - - // ENV file path - const char *indiskel = getenv("INDISKEL"); - if (indiskel) - { - pathName = indiskel; - - IDLog("Using INDISKEL %s\n", pathName.c_str()); - return pathName; - } - - // absolute file path - if (stat(fileName.c_str(), &st) == 0) - { - pathName = fileName; - IDLog("Using %s\n", pathName.c_str()); - return pathName; - } - - // get base name of file - const size_t lastSlashIdx = fileName.find_last_of("\\/"); - if (std::string::npos != lastSlashIdx) - { - fileName.erase(0, lastSlashIdx + 1); - } - - const char *indiprefix = getenv("INDIPREFIX"); - if (indiprefix) - { -#if defined(OSX_EMBEDED_MODE) - pathName = std::string(indiprefix) + "/Contents/Resources/" + fileName; -#elif defined(__APPLE__) - pathName = std::string(indiprefix) + "/Contents/Resources/DriverSupport/" + fileName; -#else - pathName = std::string(indiprefix) + "/share/indi/" + fileName; -#endif - } - else - { - // TODO : pathName = std::string(DATA_INSTALL_DIR) + "/" + fileName; - } - IDLog("Using prefix %s\n", pathName.c_str()); - return pathName; - } - - bool BaseDevice::buildSkeleton(const char *filename) - { - D_PTR(BaseDevice); - - LilXmlDocument document = d->xmlParser.readFromFile(sGetSheletonFilePath(filename)); - - if (!document.isValid()) - { - IDLog("Unable to parse skeleton XML: %s", d->xmlParser.errorMessage()); - return false; - } - - char errmsg[MAXRBUF]; - - for (const auto &element : document.root().getElements()) - { - buildProp(element, errmsg, true); - } - - return true; - } - - int BaseDevice::buildProp(const HYDROGEN::LilXmlElement &root, char *errmsg, bool isDynamic) - { - D_PTR(BaseDevice); - - // only for check, #PS: remove - { - char *rname, *rdev; - if (crackDN(root.handle(), &rdev, &rname, errmsg) < 0) - return -1; - } - - // find type of tag - static const std::map tagTypeName = - { - {HYDROGEN_NUMBER, "defNumberVector"}, - {HYDROGEN_SWITCH, "defSwitchVector"}, - {HYDROGEN_TEXT, "defTextVector"}, - {HYDROGEN_LIGHT, "defLightVector"}, - {HYDROGEN_BLOB, "defBLOBVector"}}; - - const auto rootTagName = root.tagName(); - const auto rootTagType = std::find_if(tagTypeName.begin(), tagTypeName.end(), [&rootTagName](const auto &it) - { return rootTagName == it.second; }); - - if (rootTagType == tagTypeName.end()) - { - snprintf(errmsg, MAXRBUF, "INDI: <%s> Unable to process tag", rootTagName.c_str()); - return -1; - } - - // - const char *propertyName = root.getAttribute("name").toCString(); - - if (getProperty(propertyName).isValid()) - { - return HYDROGEN_PROPERTY_DUPLICATED; - } - - if (d->deviceName.empty()) - d->deviceName = root.getAttribute("device").toString(); - - HYDROGEN::Property property; - switch (rootTagType->first) - { - case HYDROGEN_NUMBER: - { - HYDROGEN::PropertyNumber typedProperty{0}; - for (const auto &element : root.getElementsByTagName("defNumber")) - { - HYDROGEN::WidgetViewNumber widget; - - widget.setParent(typedProperty.getNumber()); - - widget.setName(element.getAttribute("name")); - widget.setLabel(element.getAttribute("label")); - - widget.setFormat(element.getAttribute("format")); - widget.setMin(element.getAttribute("min")); - widget.setMax(element.getAttribute("max")); - widget.setStep(element.getAttribute("step")); - - widget.setValue(element.context().toDoubleSexa()); - - if (!widget.isNameMatch("")) - typedProperty.push(std::move(widget)); - } - property = typedProperty; - break; - } - case HYDROGEN_SWITCH: - { - HYDROGEN::PropertySwitch typedProperty{0}; - typedProperty.setRule(root.getAttribute("rule")); - for (const auto &element : root.getElementsByTagName("defSwitch")) - { - HYDROGEN::WidgetViewSwitch widget; - - widget.setParent(typedProperty.getSwitch()); - - widget.setName(element.getAttribute("name")); - widget.setLabel(element.getAttribute("label")); - - widget.setState(element.context()); - - if (!widget.isNameMatch("")) - typedProperty.push(std::move(widget)); - } - property = typedProperty; - break; - } - - case HYDROGEN_TEXT: - { - HYDROGEN::PropertyText typedProperty{0}; - for (const auto &element : root.getElementsByTagName("defText")) - { - HYDROGEN::WidgetViewText widget; - - widget.setParent(typedProperty.getText()); - - widget.setName(element.getAttribute("name")); - widget.setLabel(element.getAttribute("label")); - - widget.setText(element.context()); - - if (!widget.isNameMatch("")) - typedProperty.push(std::move(widget)); - } - property = typedProperty; - break; - } - - case HYDROGEN_LIGHT: - { - HYDROGEN::PropertyLight typedProperty{0}; - for (const auto &element : root.getElementsByTagName("defLight")) - { - HYDROGEN::WidgetViewLight widget; - - widget.setParent(typedProperty.getLight()); - - widget.setName(element.getAttribute("name")); - widget.setLabel(element.getAttribute("label")); - - widget.setState(element.context()); - - if (!widget.isNameMatch("")) - typedProperty.push(std::move(widget)); - } - property = typedProperty; - break; - } - - case HYDROGEN_BLOB: - { - HYDROGEN::PropertyBlob typedProperty{0}; - typedProperty.setBlobDeleter([](void *&blob) - { -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - IDSharedBlobFree(blob); -#else - free(blob); -#endif - blob = nullptr; }); - for (const auto &element : root.getElementsByTagName("defBLOB")) - { - HYDROGEN::WidgetViewBlob widget; - - widget.setParent(typedProperty.getBLOB()); - - widget.setName(element.getAttribute("name")); - widget.setLabel(element.getAttribute("label")); - - widget.setFormat(element.getAttribute("format")); - - if (!widget.isNameMatch("")) - typedProperty.push(std::move(widget)); - } - property = typedProperty; - break; - } - - case HYDROGEN_UNKNOWN: // it will never happen - return -1; - } - - if (!property.isValid()) - { - IDLog("%s: invalid name '%s'\n", propertyName, rootTagName.c_str()); - return 0; - } - - if (property.isEmpty()) - { - IDLog("%s: %s with no valid members\n", propertyName, rootTagName.c_str()); - return 0; - } - - property.setBaseDevice(*this); - property.setName(propertyName); - property.setDynamic(isDynamic); - property.setDeviceName(getDeviceName()); - - property.setLabel(root.getAttribute("label")); - property.setGroupName(root.getAttribute("group")); - property.setState(root.getAttribute("state")); - property.setTimeout(root.getAttribute("timeout")); - - if (rootTagType->first != HYDROGEN_LIGHT) - { - property.setPermission(root.getAttribute("perm").toIPerm()); - } - - d->addProperty(property); - - // IDLog("Adding number property %s to list.\n", property.getName()); - d->mediateNewProperty(property); - - return (0); - } - - bool BaseDevice::isConnected() const - { - auto svp = getSwitch(HYDROGEN::SP::CONNECTION); - if (!svp) - return false; - - auto sp = svp.findWidgetByName("CONNECT"); - - return sp && sp->getState() == ISS_ON && svp.getState() == IPS_OK; - } - - void BaseDevice::attach() - { - D_PTR(BaseDevice); - d->mediateNewDevice(*this); - } - - void BaseDevice::detach() - { - D_PTR(BaseDevice); - d->mediateRemoveDevice(*this); - } - - // helper for BaseDevice::setValue - template - static void for_property( - // XMLEle *root, - const LilXmlElement &root, - HYDROGEN::Property &property, - const std::function *)> &function) - { - TypedProperty typedProperty = property; - - for (const auto &element : root.getElements()) - { - auto *item = typedProperty.findWidgetByName(element.getAttribute("name")); - if (item) - function(element, item); - } - - typedProperty.emitUpdate(); - } - - /* - * return 0 if ok else -1 with reason in errmsg - */ - int BaseDevice::setValue(const HYDROGEN::LilXmlElement &root, char *errmsg) - { - D_PTR(BaseDevice); - - if (!root.getAttribute("name").isValid()) - { - snprintf(errmsg, MAXRBUF, "INDI: <%s> unable to find name attribute", root.tagName().c_str()); - return -1; - } - - // check message - checkMessage(root.handle()); - - // find type of tag - static const std::map tagTypeName = - { - {HYDROGEN_NUMBER, "setNumberVector"}, - {HYDROGEN_SWITCH, "setSwitchVector"}, - {HYDROGEN_TEXT, "setTextVector"}, - {HYDROGEN_LIGHT, "setLightVector"}, - {HYDROGEN_BLOB, "setBLOBVector"}}; - - const auto rootTagName = root.tagName(); - const auto rootTagType = std::find_if(tagTypeName.begin(), tagTypeName.end(), [&rootTagName](const auto &it) - { return rootTagName == it.second; }); - - if (rootTagType == tagTypeName.end()) - { - snprintf(errmsg, MAXRBUF, "INDI: <%s> Unable to process tag", rootTagName.c_str()); - return -1; - } - - // update generic values - const char *propertyName = root.getAttribute("name").toCString(); - - HYDROGEN::Property property = getProperty(propertyName, rootTagType->first); - - if (!property.isValid()) - { - snprintf(errmsg, MAXRBUF, "INDI: Could not find property %s in %s", propertyName, getDeviceName()); - return -1; - } - - // 1. set overall property state, if any - { - bool ok = false; - property.setState(root.getAttribute("state").toIPState(&ok)); - - if (!ok) - { - snprintf(errmsg, MAXRBUF, "INDI: <%s> bogus state %s for %s", rootTagName.c_str(), root.getAttribute("state").toCString(), - propertyName); - return -1; - } - } - - // 2. allow changing the timeout - { - AutoCNumeric locale; - bool ok = false; - auto timeoutValue = root.getAttribute("timeout").toDouble(&ok); - - if (ok) - property.setTimeout(timeoutValue); - } - - // update specific values - switch (rootTagType->first) - { - case HYDROGEN_NUMBER: - { - AutoCNumeric locale; - for_property(root, property, [](const LilXmlElement &element, auto *item) - { - item->setValue(element.context()); - - // Permit changing of min/max - if (auto min = element.getAttribute("min")) item->setMin(min); - if (auto max = element.getAttribute("max")) item->setMax(max); }); - locale.Restore(); - break; - } - - case HYDROGEN_SWITCH: - { - for_property(root, property, [](const LilXmlElement &element, auto *item) - { item->setState(element.context()); }); - break; - } - - case HYDROGEN_TEXT: - { - for_property(root, property, [](const LilXmlElement &element, auto *item) - { item->setText(element.context()); }); - break; - } - - case HYDROGEN_LIGHT: - { - for_property(root, property, [](const LilXmlElement &element, auto *item) - { item->setState(element.context()); }); - break; - } - - case HYDROGEN_BLOB: - { - if (d->setBLOB(PropertyBlob(property), root, errmsg) < 0) - return -1; - break; - } - - case HYDROGEN_UNKNOWN: // it will never happen - return -1; - } - - d->mediateUpdateProperty(property); - - return 0; - } - -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - static bool sSharedToBlob(const HYDROGEN::LilXmlElement &element, HYDROGEN::WidgetViewBlob &widget) - { - auto attachementId = element.getAttribute("attached-data-id"); - - if (!attachementId.isValid()) - { - return false; - } - - auto size = element.getAttribute("size"); - // Client mark blob that can be attached directly - - // FIXME: Where is the blob data buffer freed at the end ? - // FIXME: blobSize is not buffer size here. Must pass it all the way through - // (while compressing shared buffer is useless) - if (auto directAttachment = element.getAttribute("attachment-direct")) - { - if (widget.getBlob()) - { - IDSharedBlobFree(widget.getBlob()); - widget.setBlobLen(0); - } - widget.setBlob(attachBlobByUid(attachementId.toString(), size)); - } - else - { - // For compatibility, copy to a modifiable memory area - widget.setBlob(realloc(widget.getBlob(), size)); - void *tmp = attachBlobByUid(attachementId.toString(), size); - memcpy(widget.getBlob(), tmp, size); - IDSharedBlobFree(tmp); - } - widget.setBlobLen(size); - - return true; - } -#endif - - /* Set BLOB vector. Process incoming data stream - * Return 0 if okay, -1 if error - */ - int BaseDevicePrivate::setBLOB(HYDROGEN::PropertyBlob property, const LilXmlElement &root, char *errmsg) - { - for (const auto &element : root.getElementsByTagName("oneBLOB")) - { - auto name = element.getAttribute("name"); - auto format = element.getAttribute("format"); - auto size = element.getAttribute("size"); - - auto widget = property.findWidgetByName(name); - - if (!name || !format || !size) - { - snprintf(errmsg, MAXRBUF, "INDI: %s.%s.%s No valid members.", - property.getDeviceName(), property.getName(), name.toCString()); - return -1; - } - - if (size.toInt() == 0) - { - continue; - } - - widget->setSize(size); -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - if (sSharedToBlob(element, *widget) == false) -#endif - { - size_t base64_encoded_size = element.context().size(); - size_t base64_decoded_size = 3 * base64_encoded_size / 4; - widget->setBlob(realloc(widget->getBlob(), base64_decoded_size)); - int blobLen = from64tobits_fast(static_cast(widget->getBlob()), element.context(), base64_encoded_size); - widget->setBlobLen(blobLen); - } - - if (format.endsWith(".z")) - { - widget->setFormat(format.toString().substr(0, format.lastIndexOf(".z"))); - - uLongf dataSize = widget->getSize() * sizeof(uint8_t); - Bytef *dataBuffer = static_cast(malloc(dataSize)); - - if (dataBuffer == nullptr) - { - strncpy(errmsg, "Unable to allocate memory for data buffer", MAXRBUF); - return -1; - } - int r = uncompress(dataBuffer, &dataSize, static_cast(widget->getBlob()), - static_cast(widget->getBlobLen())); - if (r != Z_OK) - { - snprintf(errmsg, MAXRBUF, "INDI: %s.%s.%s compression error: %d", - property.getDeviceName(), property.getName(), widget->getName(), r); - free(dataBuffer); - return -1; - } - widget->setSize(dataSize); -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - IDSharedBlobFree(widget->getBlob()); -#else - free(widget->getBlob()); -#endif - widget->setBlob(dataBuffer); - } - else - { - widget->setFormat(format); - } - - property.emitUpdate(); - } - - return 0; - } - - void BaseDevice::setDeviceName(const char *dev) - { - D_PTR(BaseDevice); - d->deviceName = dev; - } - - const char *BaseDevice::getDeviceName() const - { - D_PTR(const BaseDevice); - return d->deviceName.data(); - } - - bool BaseDevice::isDeviceNameMatch(const char *otherName) const - { - D_PTR(const BaseDevice); - return d->deviceName == otherName; - } - - bool BaseDevice::isDeviceNameMatch(const std::string &otherName) const - { - D_PTR(const BaseDevice); - return d->deviceName == otherName; - } - - /* add message to queue - * N.B. don't put carriage control in msg, we take care of that. - */ - void BaseDevice::checkMessage(XMLEle *root) - { - XMLAtt *ap; - ap = findXMLAtt(root, "message"); - - if (ap) - doMessage(root); - } - - /* Store msg in queue */ - void BaseDevice::doMessage(XMLEle *msg) - { - XMLAtt *message; - XMLAtt *time_stamp; - - char msgBuffer[MAXRBUF]; - - /* prefix our timestamp if not with msg */ - time_stamp = findXMLAtt(msg, "timestamp"); - - /* finally! the msg */ - message = findXMLAtt(msg, "message"); - if (!message) - return; - - if (time_stamp) - snprintf(msgBuffer, MAXRBUF, "%s: %s ", valuXMLAtt(time_stamp), valuXMLAtt(message)); - else - snprintf(msgBuffer, MAXRBUF, "%s: %s ", indi_timestamp(), valuXMLAtt(message)); - - std::string finalMsg = msgBuffer; - - // Prepend to the log - addMessage(finalMsg); - } - - void BaseDevice::addMessage(const std::string &msg) - { - D_PTR(BaseDevice); - std::unique_lock guard(d->m_Lock); - d->messageLog.push_back(msg); - guard.unlock(); - - d->mediateNewMessage(*this, int(d->messageLog.size() - 1)); - } - - const std::string &BaseDevice::messageQueue(size_t index) const - { - D_PTR(const BaseDevice); - std::lock_guard lock(d->m_Lock); - assert(index < d->messageLog.size()); - return d->messageLog.at(index); - } - - const std::string &BaseDevice::lastMessage() const - { - D_PTR(const BaseDevice); - std::lock_guard lock(d->m_Lock); - assert(d->messageLog.size() != 0); - return d->messageLog.back(); - } - - bool BaseDevice::isValid() const - { - D_PTR(const BaseDevice); - return d->valid; - } - - void BaseDevice::watchProperty(const char *name, const std::function &callback, WATCH watch) - { - D_PTR(BaseDevice); - d->watchPropertyMap[name].callback = callback; - d->watchPropertyMap[name].watch = watch; - - // call callback function if property already exists - HYDROGEN::Property property = getProperty(name); - if (property.isValid()) - { - callback(property); - } - } - - void BaseDevice::registerProperty(const HYDROGEN::Property &property) - { - D_PTR(BaseDevice); - - if (property.getType() == HYDROGEN_UNKNOWN) - return; - - auto pContainer = getProperty(property.getName(), property.getType()); - - if (pContainer.isValid()) - pContainer.setRegistered(true); - else - d->addProperty(property); - } - - // #PS: TODO remove - void BaseDevice::registerProperty(const HYDROGEN::Property &property, HYDROGEN_PROPERTY_TYPE) - { - registerProperty(property); - } - - const char *BaseDevice::getDriverName() const - { - auto driverName = getText("DRIVER_INFO").findWidgetByName("DRIVER_NAME"); - - return driverName ? driverName->getText() : nullptr; - } - - const char *BaseDevice::getDriverExec() const - { - auto driverExec = getText("DRIVER_INFO").findWidgetByName("DRIVER_EXEC"); - return driverExec ? driverExec->getText() : nullptr; - } - - const char *BaseDevice::getDriverVersion() const - { - auto driverVersion = getText("DRIVER_INFO").findWidgetByName("DRIVER_VERSION"); - return driverVersion ? driverVersion->getText() : nullptr; - } - - uint16_t BaseDevice::getDriverInterface() const - { - auto driverInterface = getText("DRIVER_INFO").findWidgetByName("DRIVER_INTERFACE"); - return driverInterface ? atoi(driverInterface->getText()) : 0; - } - - void BaseDevice::setMediator(HYDROGEN::BaseMediator *mediator) - { - D_PTR(BaseDevice); - d->mediator = mediator; - } - - HYDROGEN::BaseMediator *BaseDevice::getMediator() const - { - D_PTR(const BaseDevice); - return d->mediator; - } - - BaseDevice *BaseDevice::operator->() - { - D_PTR(BaseDevice); - return &d->self; - } - - BaseDevice::operator BaseDevice *() - { - D_PTR(BaseDevice); - return isValid() ? &d->self : nullptr; - } - -} - -#if defined(_MSC_VER) -#undef snprintf -#pragma warning(pop) -#endif diff --git a/src/core/base/basedevice.h b/src/core/base/basedevice.h deleted file mode 100644 index 0191eda6..00000000 --- a/src/core/base/basedevice.h +++ /dev/null @@ -1,314 +0,0 @@ -/******************************************************************************* - Copyright(c) 2011 Jasem Mutlaq. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#pragma once - -#include "hydrogenbase.h" -#include "hydrogenproperty.h" -#include "hydrogenproperties.h" - -#include -#include -#include - -#include "hydrogenpropertytext.h" -#include "hydrogenpropertynumber.h" -#include "hydrogenpropertyswitch.h" -#include "hydrogenpropertylight.h" -#include "hydrogenpropertyblob.h" - -// #define MAXRBUF 2048 // #PS: defined in indibase.h - -/** @class HYDROGEN::BaseDevice - * @brief Class to provide basic INDI device functionality. - * - * HYDROGEN::BaseDevice is the base device for all INDI devices and contains a list of all properties defined by the device either explicity or via a skeleton file. - * You don't need to subclass HYDROGEN::BaseDevice class directly, it is inheritied by HYDROGEN::DefaultDevice which takes care of building a standard INDI device. Moreover, HYDROGEN::BaseClient - * maintains a list of HYDROGEN::BaseDevice objects as they get defined from the INDI server, and those objects may be accessed to retrieve information on the object properties or message log. - * - * @author Jasem Mutlaq - */ -namespace HYDROGEN -{ - - class LilXmlElement; - class BaseDevicePrivate; - class BaseDevice - { - DECLARE_PRIVATE(BaseDevice) - public: - typedef HYDROGEN::Properties Properties; - // typedef std::vector Properties; - - /*! INDI error codes. */ - enum HYDROGEN_ERROR - { - HYDROGEN_DEVICE_NOT_FOUND = -1, /*!< INDI Device was not found. */ - HYDROGEN_PROPERTY_INVALID = -2, /*!< Property has an invalid syntax or attribute. */ - HYDROGEN_PROPERTY_DUPLICATED = -3, /*!< INDI Device was not found. */ - HYDROGEN_DISPATCH_ERROR = -4 /*!< Dispatching command to driver failed. */ - }; - - /*! Used for switch Enabled/Disabled or On/Off type properties */ - enum - { - HYDROGEN_ENABLED, - HYDROGEN_DISABLED - }; - - /*! Used for watchProperty callback method. */ - enum WATCH - { - WATCH_NEW = 0, /*!< Applies to discovered properties only. */ - WATCH_UPDATE, /*!< Applies to updated properties only. */ - WATCH_NEW_OR_UPDATE /*!< Applies when a property appears or is updated, i.e. both of the above. */ - }; - - /** @brief The DRIVER_INTERFACE enum defines the class of devices the driver implements. A driver may implement one or more interfaces. */ - enum DRIVER_INTERFACE - { - GENERAL_INTERFACE = 0, /**< Default interface for all INDI devices */ - TELESCOPE_INTERFACE = (1 << 0), /**< Telescope interface, must subclass HYDROGEN::Telescope */ - CCD_INTERFACE = (1 << 1), /**< CCD interface, must subclass HYDROGEN::CCD */ - GUIDER_INTERFACE = (1 << 2), /**< Guider interface, must subclass HYDROGEN::GuiderInterface */ - FOCUSER_INTERFACE = (1 << 3), /**< Focuser interface, must subclass HYDROGEN::FocuserInterface */ - FILTER_INTERFACE = (1 << 4), /**< Filter interface, must subclass HYDROGEN::FilterInterface */ - DOME_INTERFACE = (1 << 5), /**< Dome interface, must subclass HYDROGEN::Dome */ - GPS_INTERFACE = (1 << 6), /**< GPS interface, must subclass HYDROGEN::GPS */ - WEATHER_INTERFACE = (1 << 7), /**< Weather interface, must subclass HYDROGEN::Weather */ - AO_INTERFACE = (1 << 8), /**< Adaptive Optics Interface */ - DUSTCAP_INTERFACE = (1 << 9), /**< Dust Cap Interface */ - LIGHTBOX_INTERFACE = (1 << 10), /**< Light Box Interface */ - DETECTOR_INTERFACE = (1 << 11), /**< Detector interface, must subclass HYDROGEN::Detector */ - ROTATOR_INTERFACE = (1 << 12), /**< Rotator interface, must subclass HYDROGEN::RotatorInterface */ - SPECTROGRAPH_INTERFACE = (1 << 13), /**< Spectrograph interface */ - CORRELATOR_INTERFACE = (1 << 14), /**< Correlators (interferometers) interface */ - AUX_INTERFACE = (1 << 15), /**< Auxiliary interface */ - - SENSOR_INTERFACE = SPECTROGRAPH_INTERFACE | DETECTOR_INTERFACE | CORRELATOR_INTERFACE - }; - - public: - BaseDevice(); - virtual ~BaseDevice(); - - public: // property - /** @brief Register the property to be able to observe and update. - * @param property any property from the HYDROGEN::PropertyXXX family. - */ - void registerProperty(const HYDROGEN::Property &property); - void registerProperty(const HYDROGEN::Property &property, - HYDROGEN_PROPERTY_TYPE type); // backward compatiblity (PentaxCCD, PkTriggerCordCCD) - - /** @brief Remove a property - * @param name name of property to be removed. Pass NULL to remove the whole device. - * @param errmsg buffer to store error message. - * @return 0 if successul, -1 otherwise. - */ - int removeProperty(const char *name, char *errmsg); - - /** @brief Call the callback function if property is available. - * @param name of property. - * @param callback as an argument of the function you can use HYDROGEN::PropertyNumber, HYDROGEN::PropertySwitch etc. - * @param watch you can decide whether the callback should be executed only once (WATCH_NEW) on discovery of the property or - * also on every change of the value (WATCH_UPDATE) or both (WATCH_NEW_OR_UPDATE) - * @note the behavior is analogous to BaseMediator::newProperty/updateProperty - */ - void watchProperty(const char *name, const std::function &callback, WATCH watch = WATCH_NEW); - - /** @brief Return a property and its type given its name. - * @param name of property to be found. - * @param type of property found. - * @return If property is found, it is returned. To be used you must use static_cast with given the type of property - * returned. - */ - Property getProperty(const char *name, HYDROGEN_PROPERTY_TYPE type = HYDROGEN_UNKNOWN) const; - - /** @brief Return a list of all properties in the device. */ - Properties getProperties(); - const Properties getProperties() const; - - public: - /** @return Return vector number property given its name */ - HYDROGEN::PropertyNumber getNumber(const char *name) const; - /** @return Return vector text property given its name */ - HYDROGEN::PropertyText getText(const char *name) const; - /** @return Return vector switch property given its name */ - HYDROGEN::PropertySwitch getSwitch(const char *name) const; - /** @return Return vector light property given its name */ - HYDROGEN::PropertyLight getLight(const char *name) const; - /** @return Return vector BLOB property given its name */ - HYDROGEN::PropertyBlob getBLOB(const char *name) const; - - public: // deprecated - /** @return Return property state */ - IPState getPropertyState(const char *name) const; - /** @return Return property permission */ - IPerm getPropertyPermission(const char *name) const; - - /** @brief Return a property and its type given its name. - * @param name of property to be found. - * @param type of property found. - * @return If property is found, the raw void * pointer to the IXXXVectorProperty is returned. To be used you must use static_cast with given the type of property - * returned. For example, INumberVectorProperty *num = static_cast getRawProperty("FOO", HYDROGEN_NUMBER); - * - * @note This is a low-level function and should not be called directly unless necessary. Use getXXX instead where XXX - * is the property type (Number, Text, Switch..etc). - */ - void *getRawProperty(const char *name, HYDROGEN_PROPERTY_TYPE type = HYDROGEN_UNKNOWN) const; - - public: - /** @brief Add message to the driver's message queue. - * @param msg Message to add. - */ - void addMessage(const std::string &msg); - void checkMessage(XMLEle *root); - void doMessage(XMLEle *msg); - - /** @return Returns a specific message. */ - const std::string &messageQueue(size_t index) const; - - /** @return Returns last message message. */ - const std::string &lastMessage() const; - - public: - /** @return True if the device exists */ - bool isValid() const; - - /** @return True if the device is connected (CONNECT=ON), False otherwise */ - bool isConnected() const; - - /** @brief indicates that the device is ready - * @note the BaseMediator::newDevice method will be called - */ - void attach(); - - /** @brief indicates that the device is being removed - * @note the BaseMediator::removeDevice method will be called - */ - void detach(); - - /** @brief Set the driver's mediator to receive notification of news devices and updated property values. */ - void setMediator(HYDROGEN::BaseMediator *mediator); - - /** @returns Get the meditator assigned to this driver */ - HYDROGEN::BaseMediator *getMediator() const; - - /** @brief Set the device name - * @param dev new device name - */ - void setDeviceName(const char *dev); - - /** @return Returns the device name */ - const char *getDeviceName() const; - - /** @brief Check that the device name matches the argument. **/ - bool isDeviceNameMatch(const char *otherName) const; - - /** @brief Check that the device name matches the argument. **/ - bool isDeviceNameMatch(const std::string &otherName) const; - - /** @return driver name - * @note This can only be valid if DRIVER_INFO is defined by the driver. - */ - const char *getDriverName() const; - - /** @return driver executable name - * @note This can only be valid if DRIVER_INFO is defined by the driver. - */ - const char *getDriverExec() const; - - /** @return driver version - * @note This can only be valid if DRIVER_INFO is defined by the driver. - */ - const char *getDriverVersion() const; - - /** @brief getDriverInterface returns ORed values of @ref HYDROGEN::BaseDevice::DRIVER_INTERFACE "DRIVER_INTERFACE". It presents the device classes supported by the driver. - * @return driver device interface descriptor. - * @note For example, to know if the driver supports CCD interface, check the retruned value: - * @code{.cpp} - * if (device.getDriverInterface() & CCD_INTERFACE) - * cout << "We received a camera!" << endl; - * @endcode - */ - uint16_t getDriverInterface() const; - - public: - /** @brief Build driver properties from a skeleton file. - * @param filename full path name of the file. - * @return true if successful, false otherwise. - * - * A skeloton file defines the properties supported by this driver. It is a list of defXXX elements enclosed by @@ - * and @@ opening and closing tags. After the properties are created, they can be rerieved, manipulated, and defined - * to other clients. - * @see An example skeleton file can be found under examples/tutorial_four_sk.xml - */ - bool buildSkeleton(const char *filename); - - /** @brief Build a property given the supplied XML element (defXXX) - * @param root XML element to parse and build. - * @param errmsg buffer to store error message in parsing fails. - * @param isDynamic set to true if property is loaded from an XML file. - * @return 0 if parsing is successful, -1 otherwise and errmsg is set - */ - int buildProp(const HYDROGEN::LilXmlElement &root, char *errmsg, bool isDynamic = false); - - /** @brief handle SetXXX commands from client */ - int setValue(const HYDROGEN::LilXmlElement &root, char *errmsg); - - /** @brief Return full path to shared INDI file (typically installed to /usr/share/indi on Linux) - * @example On Linux, calling getSharedFilePath("drivers.xml") will return /usr/share/indi/drivers.xml - */ - static std::string getSharedFilePath(std::string fileName); - - public: - HYDROGEN_DEPRECATED("Do not use BaseDevice as pointer.") - operator BaseDevice *(); - - HYDROGEN_DEPRECATED("Do not use BaseDevice as pointer.") - BaseDevice *operator->(); - - HYDROGEN_DEPRECATED("Use comparison to true.") - bool operator!=(std::nullptr_t) const - { - return isValid(); - } - - HYDROGEN_DEPRECATED("Use comparison to false.") - bool operator==(std::nullptr_t) const - { - return !isValid(); - } - - operator bool() const - { - return isValid(); - } - operator bool() - { - return isValid(); - } - - protected: - friend class AbstractBaseClientPrivate; - std::shared_ptr d_ptr; - BaseDevice(BaseDevicePrivate &dd); - BaseDevice(const std::shared_ptr &dd); - }; - -} diff --git a/src/core/base/basedevice_p.h b/src/core/base/basedevice_p.h deleted file mode 100644 index 966783ee..00000000 --- a/src/core/base/basedevice_p.h +++ /dev/null @@ -1,152 +0,0 @@ -/******************************************************************************* - Copyright(c) 2011 Jasem Mutlaq. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#pragma once - -#include "util/macro.hpp" -#include "basedevice.h" -#include "lilxml.hpp" -#include "hydrogenbase.h" - -#include -#include -#include -#include -#include - -#include "hydrogenpropertyblob.h" -#include "hydrogenlilxml.hpp" - -namespace HYDROGEN -{ - - class BaseDevice; - class BaseDevicePrivate - { - public: - BaseDevicePrivate(); - virtual ~BaseDevicePrivate(); - - /** @brief Parse and store BLOB in the respective vector */ - int setBLOB(HYDROGEN::PropertyBlob propertyBlob, const HYDROGEN::LilXmlElement &root, char *errmsg); - - void emitWatchProperty(const HYDROGEN::Property &property, bool isNew) - { - auto it = watchPropertyMap.find(property.getName()); - if (it != watchPropertyMap.end()) - { - if ( - (it->second.watch == BaseDevice::WATCH_NEW_OR_UPDATE) || - (it->second.watch == BaseDevice::WATCH_NEW && isNew) || - (it->second.watch == BaseDevice::WATCH_UPDATE && !isNew)) - it->second.callback(property); - } - } - - void addProperty(const HYDROGEN::Property &property) - { - { - std::unique_lock lock(m_Lock); - pAll.push_back(property); - } - - emitWatchProperty(property, true); - } - - public: // mediator - void mediateNewDevice(BaseDevice baseDevice) - { - if (mediator) - { - mediator->newDevice(baseDevice); - } - } - - void mediateRemoveDevice(BaseDevice baseDevice) - { - if (mediator) - { - mediator->removeDevice(baseDevice); - } - } - - void mediateNewProperty(Property property) - { - if (mediator) - { - mediator->newProperty(property); - } - } - - void mediateUpdateProperty(Property property) - { - emitWatchProperty(property, false); - if (mediator) - { - mediator->updateProperty(property); - } - } - - void mediateRemoveProperty(Property property) - { - if (mediator) - { - mediator->removeProperty(property); - } - } - - void mediateNewMessage(BaseDevice baseDevice, int messageID) - { - if (mediator) - { - mediator->newMessage(baseDevice, messageID); - } - } - - public: - static std::shared_ptr invalid() - { - static struct Invalid : public BaseDevicePrivate - { - Invalid() { valid = false; } - } invalid; - return make_shared_weak(&invalid); - } - - public: - struct WatchDetails - { - std::function callback; - BaseDevice::WATCH watch{BaseDevice::WATCH_NEW}; - }; - - public: - BaseDevice self{make_shared_weak(this)}; // backward compatibile (for operators as pointer) - std::string deviceName; - BaseDevice::Properties pAll; - std::map watchPropertyMap; - LilXmlParser xmlParser; - - HYDROGEN::BaseMediator *mediator{nullptr}; - std::deque messageLog; - mutable std::mutex m_Lock; - - bool valid{true}; - }; - -} diff --git a/src/core/base/hydrogenapi.h.in b/src/core/base/hydrogenapi.h.in deleted file mode 100644 index 720a13bd..00000000 --- a/src/core/base/hydrogenapi.h.in +++ /dev/null @@ -1,520 +0,0 @@ -#if 0 -HYDROGEN -Copyright (C) 2003 Elwood C. Downey -2022 Ludovic Pollet - -This library is free software; -you can redistribute it and / or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; -either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; -without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; -if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA - -#endif - -#pragma once - -#include - -/** \mainpage Instrument Neutral Distributed Interface HYDROGEN - * -\section Introduction - -

        HYDROGEN is a simple XML-like communications protocol described for interactive and automated remote control of diverse instrumentation. HYDROGEN is small, easy to parse, and stateless.

        -

        A full description of the HYDROGEN protocol is detailed in the HYDROGEN white paper

        - -

        Under HYDROGEN, any number of clients can connect to any number of drivers running one or more devices. It is a true N-to-N server/client architecture topology -allowing for reliable deployments of several HYDROGEN server, client, and driver instances distrubted across different systems in different physical and logical locations.

        - -

        The basic premise of HYDROGEN is this: Drivers are responsible for defining their functionality in terms of Properties. Clients are not aware of such properties until they establish connection with the driver -and start receiving streams of defined properties. Each property encompases some functionality or information about the device. These include number, text, switch, light, and BLOB properties.

        - -

        For example, all devices define the CONNECTION vector switch property, which is compromised of two switches:

        -
          -
        1. CONNECT: Connect to the device.
        2. -
        3. DISCONNECT: Disconnect to the device.
        4. -
        - -

        Therefore, a client, whether it is a GUI client that represents such property as buttons, or a Python script that parses the properties, can change the state -of the switch to cause the desired action.

        -

        Not all properties are equal. A few properties are reserved to ensure interoperality among different clients that want to target a specific functionality. -These Standard Properties ensure that different clients agree of a common set of properties with specific meaning since HYDROGEN does not impose any specific meaning on the properties themselves.

        - -

        HYDROGEN server acts as a convenient hub to route communications between clients and drivers. While it is not strictly required for controlling driver, it offers many queue and routing capabilities.

        - -\section Audience Intended Audience - -HYDROGEN is intended for developers seeking to add support for their devices in HYDROGEN. Any HYDROGEN driver can be operated from numerous cross-platform cross-architecture clients. - -\section Development Developing under HYDROGEN - -

        Please refere to the HYDROGEN Developers Manual for a complete guide on HYDROGEN's driver developemnt framework.

        - -

        The HYDROGEN Library API is divided into the following main sections:

        - - -\section Tutorials - -HYDROGEN Library includes a number of tutorials to illustrate development of HYDROGEN drivers. Check out the examples provided with HYDROGEN library. - -\section Simulators - -Simulators provide a great framework to test drivers and equipment alike. HYDROGEN Library provides the following simulators: -
          -
        • @ref ScopeSim "Telescope Simulator": Offers GOTO capability, motion control, guiding, and ability to set Periodic Error (PE) which is read by the CCD simulator when generating images.
        • -
        • @ref CCDSim "CCD Simulator": Offers a very flexible CCD simulator with a primary CCD chip and a guide chip. The simulator generate images based on the RA & DEC coordinates it - snoops from the telescope driver using General Star Catalog (GSC). Please note that you must install GSC for the CCD simulator to work properly. Furthermore, - The simulator snoops FWHM from the focuser simulator which affects the generated images focus. All images are generated in standard FITS format.
        • -
        • @ref GuideSim "Guide Simulator": Simple dedicated Guide Simulator. -
        • @ref FilterSim "Filter Wheel Simulator": Offers a simple simulator to change filter wheels and their corresponding designations.
        • -
        • @ref FocusSim "Focuser Simulator": Offers a simple simualtor for an absolute position focuser. It generates a simulated FWHM value that may be used by other simulator such as the CCD simulator.
        • -
        • @ref DomeSim "Dome Simulator": Offers a simple simulator for an absolute position dome with shutter. -
        • @ref GPSSimulator "GPS Simulator": Offers a simple simulator for GPS devices that send time and location data to the client and other drivers. -
        - -\section Help - -You can find information on HYDROGEN development in the HYDROGEN Library site. Furthermore, you can discuss HYDROGEN related issues on the HYDROGEN development mailing list. - -\author Jasem Mutlaq -\author Elwood Downey - -For a full list of contributors, please check Contributors page on Github. - -*/ - -/** \file hydrogenapi.h - \brief Constants and Data structure definitions for the interface to the reference HYDROGEN C API implementation. - \author Elwood C. Downey -*/ - -/******************************************************************************* - * HYDROGEN wire protocol version implemented by this API. - * N.B. this is indepedent of the API itself. - */ - -#define HYDROGENV 1.7 - -/* HYDROGEN Library version */ -#define HYDROGEN_VERSION_MAJOR @CMAKE_HYDROGEN_VERSION_MAJOR@ -#define HYDROGEN_VERSION_MINOR @CMAKE_HYDROGEN_VERSION_MINOR@ -#define HYDROGEN_VERSION_RELEASE @CMAKE_HYDROGEN_VERSION_RELEASE@ - -/******************************************************************************* - * Manifest constants - */ - -/** - * @typedef ISState - * @brief Switch state. - */ -typedef enum -{ -ISS_OFF = 0, /*!< Switch is OFF */ -ISS_ON /*!< Switch is ON */ -} ISState; - -/** - * @typedef IPState - * @brief Property state. - */ -typedef enum -{ -IPS_IDLE = 0, /*!< State is idle */ -IPS_OK, /*!< State is ok */ -IPS_BUSY, /*!< State is busy */ -IPS_ALERT /*!< State is alert */ -} IPState; - -/** - * @typedef ISRule - * @brief Switch vector rule hint. - */ -typedef enum -{ -ISR_1OFMANY, /*!< Only 1 switch of many can be ON (e.g. radio buttons) */ -ISR_ATMOST1, /*!< At most one switch can be ON, but all switches can be off. It is similar to ISR_1OFMANY with the exception that all switches can be off. */ -ISR_NOFMANY /*!< Any number of switches can be ON (e.g. check boxes) */ -} ISRule; - -/** - * @typedef IPerm - * @brief Permission hint, with respect to client. - */ -typedef enum -{ -IP_RO, /*!< Read Only */ -IP_WO, /*!< Write Only */ -IP_RW /*!< Read & Write */ -} IPerm; - -// The XML strings for these attributes may be any length but implementations -// are only obligued to support these lengths for the various string attributes. -#define MAXHYDROGENNAME 64 -#define MAXHYDROGENLABEL 64 -#define MAXHYDROGENDEVICE 64 -#define MAXHYDROGENGROUP 64 -#define MAXHYDROGENFORMAT 64 -#define MAXHYDROGENBLOBFMT 64 -#define MAXHYDROGENTSTAMP 64 -#define MAXHYDROGENMESSAGE 255 - -/******************************************************************************* - * Typedefs for each HYDROGEN Property type. - * - * INumber.format may be any printf-style appropriate for double - * or style "m" to create sexigesimal using the form "%.m" where - * is the total field width. - * is the width of the fraction. valid values are: - * 9 -> :mm:ss.ss - * 8 -> :mm:ss.s - * 6 -> :mm:ss - * 5 -> :mm.m - * 3 -> :mm - * - * examples: - * - * to produce use - * - * "-123:45" %7.3m - * " 0:01:02" %9.6m - */ - -/** - * @struct IText - * @brief One text descriptor. - */ -typedef struct _IText -{ -/** Index name */ -char name[MAXHYDROGENNAME]; - /** Short description */ - char label[MAXHYDROGENLABEL]; - /** Allocated text string */ - char *text; - /** Pointer to parent */ - struct _ITextVectorProperty *tvp; - /** Helper info */ - void *aux0; - /** Helper info */ - void *aux1; -} IText; - -/** - * @struct _ITextVectorProperty - * @brief Text vector property descriptor. - */ -typedef struct _ITextVectorProperty -{ - /** Device name */ - char device[MAXHYDROGENDEVICE]; - /** Property name */ - char name[MAXHYDROGENNAME]; - /** Short description */ - char label[MAXHYDROGENLABEL]; - /** GUI grouping hint */ - char group[MAXHYDROGENGROUP]; - /** Client accessibility hint */ - IPerm p; - /** Current max time to change, secs */ - double timeout; - /** Current property state */ - IPState s; - /** Texts comprising this vector */ - IText *tp; - /** Dimension of tp[] */ - int ntp; - /** ISO 8601 timestamp of this event */ - char timestamp[MAXHYDROGENTSTAMP]; - /** Helper info */ - void *aux; -} ITextVectorProperty; - -/** - * @struct INumber - * @brief One number descriptor. - */ -typedef struct _INumber -{ - /** Index name */ - char name[MAXHYDROGENNAME]; - /** Short description */ - char label[MAXHYDROGENLABEL]; - /** GUI display format, see above */ - char format[MAXHYDROGENFORMAT]; - /** Range min, ignored if min == max */ - double min; - /** Range max, ignored if min == max */ - double max; - /** Step size, ignored if step == 0 */ - double step; - /** Current value */ - double value; - /** Pointer to parent */ - struct _INumberVectorProperty *nvp; - /** Helper info */ - void *aux0; - /** Helper info */ - void *aux1; -} INumber; - -/** - * @struct _INumberVectorProperty - * @brief Number vector property descriptor. - * - * INumber.format may be any printf-style appropriate for double or style - * "m" to create sexigesimal using the form "%\.\m" where:\n - * \ is the total field width.\n - * \ is the width of the fraction. valid values are:\n - * 9 -> \:mm:ss.ss \n - * 8 -> \:mm:ss.s \n - * 6 -> \:mm:ss \n - * 5 -> \:mm.m \n - * 3 -> \:mm \n - * - * examples:\n - * - * To produce "-123:45", use \%7.3m \n - * To produce " 0:01:02", use \%9.6m - */ -typedef struct _INumberVectorProperty -{ - /** Device name */ - char device[MAXHYDROGENDEVICE]; - /** Property name */ - char name[MAXHYDROGENNAME]; - /** Short description */ - char label[MAXHYDROGENLABEL]; - /** GUI grouping hint */ - char group[MAXHYDROGENGROUP]; - /** Client accessibility hint */ - IPerm p; - /** Current max time to change, secs */ - double timeout; - /** current property state */ - IPState s; - /** Numbers comprising this vector */ - INumber *np; - /** Dimension of np[] */ - int nnp; - /** ISO 8601 timestamp of this event */ - char timestamp[MAXHYDROGENTSTAMP]; - /** Helper info */ - void *aux; -} INumberVectorProperty; - -/** - * @struct ISwitch - * @brief One switch descriptor. - */ -typedef struct _ISwitch -{ - /** Index name */ - char name[MAXHYDROGENNAME]; - /** Switch label */ - char label[MAXHYDROGENLABEL]; - /** Switch state */ - ISState s; - /** Pointer to parent */ - struct _ISwitchVectorProperty *svp; - /** Helper info */ - void *aux; -} ISwitch; - -/** - * @struct _ISwitchVectorProperty - * @brief Switch vector property descriptor. - */ -typedef struct _ISwitchVectorProperty -{ - /** Device name */ - char device[MAXHYDROGENDEVICE]; - /** Property name */ - char name[MAXHYDROGENNAME]; - /** Short description */ - char label[MAXHYDROGENLABEL]; - /** GUI grouping hint */ - char group[MAXHYDROGENGROUP]; - /** Client accessibility hint */ - IPerm p; - /** Switch behavior hint */ - ISRule r; - /** Current max time to change, secs */ - double timeout; - /** Current property state */ - IPState s; - /** Switches comprising this vector */ - ISwitch *sp; - /** Dimension of sp[] */ - int nsp; - /** ISO 8601 timestamp of this event */ - char timestamp[MAXHYDROGENTSTAMP]; - /** Helper info */ - void *aux; -} ISwitchVectorProperty; - -/** - * @struct ILight - * @brief One light descriptor. - */ -typedef struct _ILight -{ - /** Index name */ - char name[MAXHYDROGENNAME]; - /** Light labels */ - char label[MAXHYDROGENLABEL]; - /** Light state */ - IPState s; - /** Pointer to parent */ - struct _ILightVectorProperty *lvp; - /** Helper info */ - void *aux; -} ILight; - -/** - * @struct _ILightVectorProperty - * @brief Light vector property descriptor. - */ -typedef struct _ILightVectorProperty -{ - /** Device name */ - char device[MAXHYDROGENDEVICE]; - /** Property name */ - char name[MAXHYDROGENNAME]; - /** Short description */ - char label[MAXHYDROGENLABEL]; - /** GUI grouping hint */ - char group[MAXHYDROGENGROUP]; - /** Current property state */ - IPState s; - /** Lights comprising this vector */ - ILight *lp; - /** Dimension of lp[] */ - int nlp; - /** ISO 8601 timestamp of this event */ - char timestamp[MAXHYDROGENTSTAMP]; - /** Helper info */ - void *aux; -} ILightVectorProperty; - -/** - * @struct IBLOB - * @brief One Blob (Binary Large Object) descriptor. - */ -typedef struct _IBLOB/* one BLOB descriptor */ -{ - /** Index name */ - char name[MAXHYDROGENNAME]; - /** Blob label */ - char label[MAXHYDROGENLABEL]; - /** Format attr */ - char format[MAXHYDROGENBLOBFMT]; - /** Allocated binary large object bytes - maybe a shared buffer: use IDSharedBlobFree to free*/ - void *blob; - /** Blob size in bytes */ - int bloblen; - /** N uncompressed bytes */ - int size; - /** Pointer to parent */ - struct _IBLOBVectorProperty *bvp; - /** Helper info */ - void *aux0; - /** Helper info */ - void *aux1; - /** Helper info */ - void *aux2; -} IBLOB; - -/** - * @struct _IBLOBVectorProperty - * @brief BLOB (Binary Large Object) vector property descriptor. - */ -typedef struct _IBLOBVectorProperty /* BLOB vector property descriptor */ -{ - /** Device name */ - char device[MAXHYDROGENDEVICE]; - /** Property name */ - char name[MAXHYDROGENNAME]; - /** Short description */ - char label[MAXHYDROGENLABEL]; - /** GUI grouping hint */ - char group[MAXHYDROGENGROUP]; - /** Client accessibility hint */ - IPerm p; - /** Current max time to change, secs */ - double timeout; - /** Current property state */ - IPState s; - /** BLOBs comprising this vector */ - IBLOB *bp; - /** Dimension of bp[] */ - int nbp; - /** ISO 8601 timestamp of this event */ - char timestamp[MAXHYDROGENTSTAMP]; - /** Helper info */ - void *aux; -} IBLOBVectorProperty; - -/** - * @brief Handy macro to find the number of elements in array a[]. Must be used - * with actual array, not pointer. - */ -#define NARRAY(a) (sizeof(a) / sizeof(a[0])) - -/** - * @brief Bails out if memory pointer is 0. Prints file and function. - */ -#define assert_mem(p) if((p) == 0) { fprintf(stderr, "%s(%s): Failed to allocate memory\n", __FILE__, __func__); exit(1); } - - -#ifdef __cplusplus -extern "C" { -#endif - -// FIXME: duplicated from hydrogendevapi.h. Can we share ? - -// Advertize support for shared blob on this platform -#define HYDROGEN_SHARED_BLOB_SUPPORT -#include "sharedblob.hpp" - -#ifdef __cplusplus -} -#endif diff --git a/src/core/base/hydrogenbase.cpp b/src/core/base/hydrogenbase.cpp deleted file mode 100644 index 7f786fc0..00000000 --- a/src/core/base/hydrogenbase.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "hydrogenbase.h" -#include "basedevice.h" - -namespace HYDROGEN -{ - - // device - - void BaseMediator::newDevice(HYDROGEN::BaseDevice) - { - } - - void BaseMediator::removeDevice(HYDROGEN::BaseDevice) - { - } - - // property - - void BaseMediator::newProperty(HYDROGEN::Property) - { - } - - void BaseMediator::updateProperty(HYDROGEN::Property) - { - } - - void BaseMediator::removeProperty(HYDROGEN::Property) - { - } - - // message - - void BaseMediator::newMessage(HYDROGEN::BaseDevice, int) - { - } - - // server - - void BaseMediator::serverConnected() - { - } - - void BaseMediator::serverDisconnected(int) - { - } - -} diff --git a/src/core/base/hydrogenbase.h b/src/core/base/hydrogenbase.h deleted file mode 100644 index 53b55ee5..00000000 --- a/src/core/base/hydrogenbase.h +++ /dev/null @@ -1,131 +0,0 @@ - -#pragma once - -#include "hydrogenapi.h" -#include "hydrogendevapi.hpp" -#include "hydrogenbasetypes.h" - -#define MAXRBUF 2048 - -/** @namespace HYDROGEN - * @brief Namespace to encapsulate INDI client, drivers, and mediator classes. - * - * Developers can subclass the base devices class to implement device specific functionality. This ensures interoperability and consistency among devices within the same family - * and reduces code overhead. - * - *
          - *
        • BaseClient: Base class for INDI clients. By subclassing BaseClient, client can easily connect to INDI server and handle device communication, command, and notifcation.
        • - *
        • BaseClientQt: Qt5 based class for INDI clients. By subclassing BaseClientQt, client can easily connect to INDI server - * and handle device communication, command, and notifcation.
        • - *
        • BaseMediator: Abstract class to provide interface for event notifications in HYDROGEN::BaseClient.
        • - *
        • BaseDevice: Base class for all INDI virtual devices as handled and stored in HYDROGEN::BaseClient. It is also the parent for all drivers.
        • - *
        • DefaultDevice: HYDROGEN::BaseDevice with extended functionality such as debug, simulation, and configuration support. - * It is the base class for all drivers and may \e only used by drivers directly, it cannot be used by clients.
        • - *
        • FilterInterface: Basic interface for filter wheels functions.
        • - *
        • GuiderInterface: Basic interface for guider (ST4) port functions.
        • - *
        • RotatorInterface: Basic interface for Rotator functions.
        • - *
        • DustCapInterface: Basic interface remotely controlled dust covers.
        • - *
        • LightBoxInterface: Basic interface for remotely controlled light boxes/switches.
        • - *
        • CCD: Base class for CCD drivers. Provides basic support for single chip CCD and CCDs with a guide head as well.
        • - *
        • Telescope: Base class for telescope drivers.
        • - *
        • FilterWheel: Base class for Filter Wheels. It implements the FilterInterface.
        • - *
        • Focuser: Base class for focusers.
        • - *
        • Rotator: Base class for rotators.
        • - *
        • Dome: Base class for domes.
        • - *
        • GPS: Base class for GPS devices.
        • - *
        • Weather: Base class for Weather devices.
        • - *
        • USBDevice: Base class for USB devices for direct read/write/control over USB.
        • - *
        • Controller: Class to handle controller inputs like joysticks and gamepads.
        • - *
        • Logger: Class to handle debugging and logging of drivers.
        • - *
        - * - * @author Jasem Mutlaq - * @author Gerry Rozema - */ -namespace HYDROGEN -{ - class BaseMediator; - class BaseClient; - class BaseClientQt; - class BaseDevice; - class DefaultDevice; - class FilterInterface; - class RotatorInterface; - class GuiderInterface; - class FocuserInterface; - class WeatherInterface; - class SensorInterface; - class DomeInterface; - class DustCapInterface; - class LightBoxInterface; - class CCD; - class Spectrograph; - class Detector; - class Telescope; - class FilterWheel; - class Focuser; - class Rotator; - class Dome; - class GPS; - class Weather; - class USBDevice; - class Property; - class PropertySwitch; - class PropertyNumber; - class PropertyText; - class PropertyLight; - class Controller; - class Logger; -} - -/** @class HYDROGEN::BaseMediator - * @brief Meditates event notification as generated by driver and passed to clients. - */ -class HYDROGEN::BaseMediator -{ -public: - virtual ~BaseMediator() = default; - -public: - /** @brief Emmited when a new device is created from INDI server. - * @param baseDevice BaseDevice instance. - */ - virtual void newDevice(HYDROGEN::BaseDevice baseDevice); - - /** @brief Emmited when a device is deleted from INDI server. - * @param baseDevice BaseDevice instance. - */ - virtual void removeDevice(HYDROGEN::BaseDevice baseDevice); - -public: - /** @brief Emmited when a new property is created for an INDI driver. - * @param property Property container. - */ - virtual void newProperty(HYDROGEN::Property property); - - /** @brief Emmited when a new property value arrives from INDI server. - * @param property Property container. - */ - virtual void updateProperty(HYDROGEN::Property property); - - /** @brief Emmited when a property is deleted for an INDI driver. - * @param property Property container. - */ - virtual void removeProperty(HYDROGEN::Property property); - -public: - /** @brief Emmited when a new message arrives from INDI server. - * @param baseDevice BaseDevice instance the message is sent to. - * @param messageID ID of the message that can be used to retrieve the message from the device's messageQueue() function. - */ - virtual void newMessage(HYDROGEN::BaseDevice baseDevice, int messageID); - -public: - /** @brief Emmited when the server is connected. */ - virtual void serverConnected(); - - /** @brief Emmited when the server gets disconnected. - * @param exit_code 0 if client was requested to disconnect from server. -1 if connection to server is terminated due to remote server disconnection. - */ - virtual void serverDisconnected(int exit_code); -}; diff --git a/src/core/base/hydrogenbasetypes.h b/src/core/base/hydrogenbasetypes.h deleted file mode 100644 index c5a80c97..00000000 --- a/src/core/base/hydrogenbasetypes.h +++ /dev/null @@ -1,83 +0,0 @@ -/******************************************************************************* - Copyright(c) 2011 Jasem Mutlaq. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#pragma once - -/*! INDI property type */ -typedef enum -{ - HYDROGEN_NUMBER, /*!< INumberVectorProperty. */ - HYDROGEN_SWITCH, /*!< ISwitchVectorProperty. */ - HYDROGEN_TEXT, /*!< ITextVectorProperty. */ - HYDROGEN_LIGHT, /*!< ILightVectorProperty. */ - HYDROGEN_BLOB, /*!< IBLOBVectorProperty. */ - HYDROGEN_UNKNOWN -} HYDROGEN_PROPERTY_TYPE; - -/*! INDI Equatorial Axis type */ -typedef enum -{ - AXIS_RA, /*!< Right Ascension Axis. */ - AXIS_DE /*!< Declination Axis. */ -} HYDROGEN_EQ_AXIS; - -/*! INDI Horizontal Axis type */ -typedef enum -{ - AXIS_AZ, /*!< Azimuth Axis. */ - AXIS_ALT /*!< Altitude Axis. */ -} HYDROGEN_HO_AXIS; - -/*! North/South Direction type */ -typedef enum -{ - DIRECTION_NORTH = 0, /*!< North direction */ - DIRECTION_SOUTH /*!< South direction */ -} HYDROGEN_DIR_NS; - -/*! West/East Direction type */ -typedef enum -{ - DIRECTION_WEST = 0, /*!< West direction */ - DIRECTION_EAST /*!< East direction */ -} HYDROGEN_DIR_WE; - -/*! INDI Error Types */ -typedef enum -{ - HYDROGEN_DEVICE_NOT_FOUND = -1, /*!< Device not found error */ - HYDROGEN_PROPERTY_INVALID = -2, /*!< Property invalid error */ - HYDROGEN_PROPERTY_DUPLICATED = -3, /*!< Property duplicated error */ - HYDROGEN_DISPATCH_ERROR = -4 /*!< Dispatch error */ -} HYDROGEN_ERROR_TYPE; - -typedef enum -{ - HYDROGEN_MONO = 0, - HYDROGEN_BAYER_RGGB = 8, - HYDROGEN_BAYER_GRBG = 9, - HYDROGEN_BAYER_GBRG = 10, - HYDROGEN_BAYER_BGGR = 11, - HYDROGEN_BAYER_CYYM = 16, - HYDROGEN_BAYER_YCMY = 17, - HYDROGEN_BAYER_YMCY = 18, - HYDROGEN_BAYER_MYYC = 19, - HYDROGEN_RGB = 100, - HYDROGEN_BGR = 101, - HYDROGEN_JPG = 200, -} HYDROGEN_PIXEL_FORMAT; diff --git a/src/core/base/hydrogencom.cpp b/src/core/base/hydrogencom.cpp deleted file mode 100644 index ac4ff932..00000000 --- a/src/core/base/hydrogencom.cpp +++ /dev/null @@ -1,1899 +0,0 @@ -/* - HYDROGEN LIB - Common routines used by all drivers - Copyright (C) 2003 by Jason Harris (jharris@30doradus.org) - Elwood C. Downey - - This is the C version of the astronomical library in KStars - modified by Jasem Mutlaq (mutlaqja@ikarustech.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -*/ - -#define _GNU_SOURCE 1 - -#include "hydrogencom.hpp" - -#include "hydrogendevapi.hpp" -#include "locale/locale_compat.hpp" -#include "base64.hpp" - -#include "config.h" - -#if defined(HAVE_LIBNOVA) -#include -#include -#include -#include -#endif // HAVE_LIBNOVA - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __linux__ -#include -#endif - -#ifdef __APPLE__ -#include -#include -#include -#endif - -#ifdef __FreeBSD__ -#include -#endif - -#if defined(BSD) && !defined(__GNU__) -#ifdef __APPLE__ -#include -#endif -#include -#endif - -#ifdef __GNU__ -#include -#endif - -#ifdef _WIN32 -#undef CX -#undef CY -#include -#endif - -#ifndef _WIN32 -#include -#include -#include -#define PARITY_NONE 0 -#define PARITY_EVEN 1 -#define PARITY_ODD 2 -#endif - -#include "userio.hpp" -#include "hydrogenuserio.hpp" - -#if defined(_MSC_VER) -#define snprintf _snprintf -#pragma warning(push) -///@todo Introduce plattform indipendent safe functions as macros to fix this -#pragma warning(disable : 4996) -#endif - -#define MAXRBUF 2048 - -static int tty_debug = 0; -static int tty_gemini_udp_format = 0; -static int tty_generic_udp_format = 0; -static int tty_sequence_number = 1; -static int tty_clear_trailing_lf = 0; - -#ifdef _WIN32 -int extractISOTime(const char *timestr, struct ln_date *iso_date) -{ - // Convert timestr to SYSTEMTIME - SYSTEMTIME st; - memset(&st, 0, sizeof(st)); - - sscanf_s(timestr, "%4d-%2d-%2dT%2d:%2d:%2d", - &st.wYear, &st.wMonth, &st.wDay, &st.wHour, &st.wMinute, &st.wSecond); - - // Convert SYSTEMTIME to FILETIME - FILETIME ft; - SystemTimeToFileTime(&st, &ft); - - // Convert FILETIME to time_t - ULARGE_INTEGER uli; - uli.LowPart = ft.dwLowDateTime; - uli.HighPart = ft.dwHighDateTime; - - time_t tt = (uli.QuadPart - 116444736000000000) / 10000000; - - // Convert time_t to struct tm - struct tm *utm = localtime(&tt); - if (!utm) - return -1; - - ln_get_date_from_tm(utm, iso_date); - return 0; -} -#else -int extractISOTime(const char *timestr, struct ln_date *iso_date) -{ - struct tm utm; - - if (strptime(timestr, "%Y/%m/%dT%H:%M:%S", &utm)) - { - ln_get_date_from_tm(&utm, iso_date); - return (0); - } - - if (strptime(timestr, "%Y-%m-%dT%H:%M:%S", &utm)) - { - ln_get_date_from_tm(&utm, iso_date); - return (0); - } - - return (-1); -} -#endif - -/* sprint the variable a in sexagesimal format into out[]. - * w is the number of spaces for the whole part. - * fracbase is the number of pieces a whole is to broken into; valid options: - * 360000: :mm:ss.ss - * 36000: :mm:ss.s - * 3600: :mm:ss - * 600: :mm.m - * 60: :mm - * return number of characters written to out, not counting final '\0'. - */ -int fs_sexa(char *out, double a, int w, int fracbase) -{ - char *out0 = out; - unsigned long n; - int d; - int f; - int m; - int s; - int isneg; - - isneg = (a < 0); - if (isneg) - a = -a; - - n = static_cast(a * fracbase + 0.5); - d = n / fracbase; - f = n % fracbase; - - if (isneg && d == 0) - out += snprintf(out, MAXHYDROGENFORMAT, "%*s-0", w - 2, ""); - else - out += snprintf(out, MAXHYDROGENFORMAT, "%*d", w, isneg ? -d : d); - - switch (fracbase) - { - case 60: // dd:mm - m = f / (fracbase / 60); - out += snprintf(out, MAXHYDROGENFORMAT, ":%02d", m); - break; - case 600: // dd:mm.m - out += snprintf(out, MAXHYDROGENFORMAT, ":%02d.%1d", f / 10, f % 10); - break; - case 3600: // dd:mm:ss - m = f / (fracbase / 60); - s = f % (fracbase / 60); - out += snprintf(out, MAXHYDROGENFORMAT, ":%02d:%02d", m, s); - break; - case 36000: // dd:mm:ss.s - m = f / (fracbase / 60); - s = f % (fracbase / 60); - out += snprintf(out, MAXHYDROGENFORMAT, ":%02d:%02d.%1d", m, s / 10, s % 10); - break; - case 360000: // dd:mm:ss.ss - m = f / (fracbase / 60); - s = f % (fracbase / 60); - out += snprintf(out, MAXHYDROGENFORMAT, ":%02d:%02d.%02d", m, s / 100, s % 100); - break; - default: - return -1; - } - - return static_cast(out - out0); -} - -/* convert sexagesimal string str AxBxC to double. - * x can be anything non-numeric. Any missing A, B or C will be assumed 0. - * optional - and + can be anywhere. - * return 0 if ok, -1 if can't find a thing. - */ -int f_scansexa(const char *str0, /* input string */ - double *dp) /* cracked value, if return 0 */ -{ - locale_char_t *orig = hydrogen_locale_C_numeric_push(); - - double a = 0, b = 0, c = 0; - char str[128]; - // char *neg; - uint8_t isNegative = 0; - int r = 0; - - /* copy str0 so we can play with it */ - strncpy(str, str0, sizeof(str) - 1); - str[sizeof(str) - 1] = '\0'; - - /* remove any spaces */ - char *i = str; - char *j = str; - while (*j != 0) - { - *i = *j++; - if (*i != ' ') - i++; - } - *i = 0; - - // This has problem process numbers in scientific notations e.g. 1e-06 - /*neg = strchr(str, '-'); - if (neg) - *neg = ' '; - */ - if (str[0] == '-') - { - isNegative = 1; - str[0] = ' '; - } - - r = sscanf(str, "%lf%*[^0-9]%lf%*[^0-9]%lf", &a, &b, &c); - - hydrogen_locale_C_numeric_pop(orig); - - if (r < 1) - return (-1); - *dp = a + b / 60 + c / 3600; - if (isNegative) - *dp *= -1; - return (0); -} - -void getSexComponents(double value, int *d, int *m, int *s) -{ - *d = (int32_t)fabs(value); - *m = (int32_t)((fabs(value) - *d) * 60.0); - *s = (int32_t)rint(((fabs(value) - *d) * 60.0 - *m) * 60.0); - - // Special case if seconds are >= 59.5 so it will be rounded by rint above - // to 60 - if (*s == 60) - { - *s = 0; - *m += 1; - } - if (*m == 60) - { - *m = 0; - *d += 1; - } - - if (value < 0) - *d *= -1; -} - -void getSexComponentsIID(double value, int *d, int *m, double *s) -{ - *d = (int32_t)fabs(value); - *m = (int32_t)((fabs(value) - *d) * 60.0); - *s = (double)(((fabs(value) - *d) * 60.0 - *m) * 60.0); - - if (value < 0) - *d *= -1; -} - -/* fill buf with properly formatted INumber string. return length */ -int numberFormat(char *buf, const char *format, double value) -{ - int w, f, s; - char m; - - if (sscanf(format, "%%%d.%d%c", &w, &f, &m) == 3 && m == 'm') - { - /* HYDROGEN sexi format */ - switch (f) - { - case 9: - s = 360000; - break; - case 8: - s = 36000; - break; - case 6: - s = 3600; - break; - case 5: - s = 600; - break; - default: - s = 60; - break; - } - return (fs_sexa(buf, value, w - f, s)); - } - else - { - /* normal printf format */ - return (snprintf(buf, MAXHYDROGENFORMAT, format, value)); - } -} - -/* log message locally. - * this has nothing to do with XML or any Clients. - */ -void IDLog(const char *fmt, ...) -{ - va_list ap; - /* JM: Since all HYDROGEN's stderr are timestampped now, we don't need to time stamp ID Log */ - /*fprintf (stderr, "%s ", indi_timestamp());*/ - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); -} - -double time_ns() -{ - struct timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = 0; - -#if defined(HAVE_TIMESPEC_GET) - timespec_get(&ts, TIME_UTC); -#elif defined(HAVE_CLOCK_GETTIME) - clock_gettime(CLOCK_REALTIME, &ts); -#elif defined(__APPLE__) // OS X does not have clock_gettime, use clock_get_time - clock_serv_t cclock; - mach_timespec_t mts; - host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); - clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - ts.tv_sec = mts.tv_sec; - ts.tv_nsec = mts.tv_nsec; -#endif - return (double)ts.tv_sec + (double)(ts.tv_nsec % 1000000000) / 1000000000.0; -} - -/* return current system time in message format */ -const char *indi_timestamp() -{ - static char ts[32]; - struct tm *tp; - time_t t; - - time(&t); - tp = gmtime(&t); - strftime(ts, sizeof(ts), "%Y-%m-%dT%H:%M:%S", tp); - return (ts); -} - -void tty_set_debug(int debug) -{ - tty_debug = debug; -} - -void tty_set_gemini_udp_format(int enabled) -{ - tty_gemini_udp_format = enabled; -} - -void tty_set_generic_udp_format(int enabled) -{ - tty_generic_udp_format = enabled; -} - -void tty_clr_trailing_read_lf(int enabled) -{ - tty_clear_trailing_lf = enabled; -} - -int tty_timeout(int fd, int timeout) -{ - return tty_timeout_microseconds(fd, timeout, 0); -} - -int tty_timeout_microseconds(int fd, long timeout_seconds, long timeout_microseconds) -{ -#ifdef _WIN32 - if (fd == -1) - return TTY_ERRNO; - - struct timeval tv; - tv.tv_sec = timeout_seconds; - tv.tv_usec = timeout_microseconds; - - fd_set readout; - FD_ZERO(&readout); - FD_SET(fd, &readout); - - int retval = select(fd + 1, &readout, NULL, NULL, &tv); - - if (retval > 0) - return TTY_OK; - else if (retval == 0) - return TTY_TIME_OUT; - else - return TTY_SELECT_ERROR; -#else - if (fd == -1) - return TTY_ERRNO; - - struct timeval tv; - fd_set readout; - int retval; - - FD_ZERO(&readout); - FD_SET(fd, &readout); - - tv.tv_sec = timeout_seconds; - tv.tv_usec = timeout_microseconds; - - retval = select(fd + 1, &readout, NULL, NULL, &tv); - - if (retval > 0) - return TTY_OK; - else if (retval == 0) - return TTY_TIME_OUT; - else - return TTY_SELECT_ERROR; -#endif -} - -int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written) -{ -#ifdef _WIN32 - if (fd == -1) - return TTY_ERRNO; - - int bytes_w = 0; - *nbytes_written = 0; - - while (nbytes > 0) - { - bytes_w = send(fd, buf + (*nbytes_written), nbytes, 0); - - if (bytes_w == SOCKET_ERROR) - return TTY_ERRNO; - - *nbytes_written += bytes_w; - nbytes -= bytes_w; - } - - return TTY_OK; -#else - if (fd == -1) - return TTY_ERRNO; - - int bytes_w = 0; - *nbytes_written = 0; - - while (nbytes > 0) - { - bytes_w = write(fd, buf + (*nbytes_written), nbytes); - - if (bytes_w < 0) - return TTY_ERRNO; - - *nbytes_written += bytes_w; - nbytes -= bytes_w; - } - - return TTY_OK; -#endif -} - -int tty_write_string(int fd, const char *buf, int *nbytes_written) -{ - int nbytes; - nbytes = (int)strlen(buf); - - return tty_write(fd, buf, nbytes, nbytes_written); -} - -int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read) -{ - return tty_read_expanded(fd, buf, nbytes, timeout, 0, nbytes_read); -} -int tty_read_expanded(int fd, char *buf, int nbytes, long timeout_seconds, long timeout_microseconds, int *nbytes_read) -{ -#ifdef _WIN32 - - if (fd == -1) - return TTY_ERRNO; - - DWORD numBytesToRead = nbytes; - DWORD bytesRead = 0; - int err = 0; - *nbytes_read = 0; - - if (nbytes <= 0) - return TTY_PARAM_ERROR; - - if (tty_debug) - IDLog("%s: Request to read %d bytes with %ld s, %ld us timeout for fd %d\n", __FUNCTION__, nbytes, timeout_seconds, timeout_microseconds, fd); - - char geminiBuffer[257] = {0}; - char *buffer = buf; - - if (tty_gemini_udp_format) - { - numBytesToRead = nbytes + 8; - buffer = geminiBuffer; - } - - while (numBytesToRead > 0) - { - if ((err = tty_timeout_microseconds(fd, timeout_seconds, timeout_microseconds))) - return err; - - if (!ReadFile((HANDLE)_get_osfhandle(fd), buffer + (*nbytes_read), numBytesToRead, &bytesRead, NULL)) - return TTY_READ_ERROR; - - if (bytesRead < 0) - return TTY_READ_ERROR; - - if (tty_debug) - { - IDLog("%lu bytes read and %lu bytes remaining...\n", bytesRead, numBytesToRead - bytesRead); - int i = 0; - for (i = *nbytes_read; static_cast(i) < static_cast((*nbytes_read + bytesRead)); i++) - IDLog("%s: buffer[%d]=%#X (%c)\n", __FUNCTION__, i, (unsigned char)buf[i], buf[i]); - } - - if (*nbytes_read == 0 && tty_clear_trailing_lf && *buffer == 0x0A) - { - if (tty_debug) - IDLog("%s: Cleared LF char left in buf\n", __FUNCTION__); - - memcpy(buffer, buffer + 1, bytesRead); - --bytesRead; - } - - *nbytes_read += bytesRead; - numBytesToRead -= bytesRead; - } - - if (tty_gemini_udp_format) - { - int *intSizedBuffer = (int *)geminiBuffer; - if (intSizedBuffer[0] != tty_sequence_number) - { - // Not the right reply just do the read again. - return tty_read_expanded(fd, buf, nbytes, timeout_seconds, timeout_microseconds, nbytes_read); - } - - *nbytes_read -= 8; - memcpy(buf, geminiBuffer + 8, *nbytes_read); - } - - return TTY_OK; - -#else - - if (fd == -1) - return TTY_ERRNO; - - int numBytesToRead = nbytes; - int bytesRead = 0; - int err = 0; - *nbytes_read = 0; - - if (nbytes <= 0) - return TTY_PARAM_ERROR; - - if (tty_debug) - IDLog("%s: Request to read %d bytes with %ld s, %ld us timeout for fd %d\n", __FUNCTION__, nbytes, timeout_seconds, timeout_microseconds, fd); - - char geminiBuffer[257] = {0}; - char *buffer = buf; - - if (tty_gemini_udp_format) - { - numBytesToRead = nbytes + 8; - buffer = geminiBuffer; - } - - while (numBytesToRead > 0) - { - if ((err = tty_timeout_microseconds(fd, timeout_seconds, timeout_microseconds))) - return err; - - bytesRead = read(fd, buffer + (*nbytes_read), ((uint32_t)numBytesToRead)); - - if (bytesRead < 0) - return TTY_READ_ERROR; - - if (tty_debug) - { - IDLog("%d bytes read and %d bytes remaining...\n", bytesRead, numBytesToRead - bytesRead); - int i = 0; - for (i = *nbytes_read; i < (*nbytes_read + bytesRead); i++) - IDLog("%s: buffer[%d]=%#X (%c)\n", __FUNCTION__, i, (unsigned char)buf[i], buf[i]); - } - - if (*nbytes_read == 0 && tty_clear_trailing_lf && *buffer == 0x0A) - { - if (tty_debug) - IDLog("%s: Cleared LF char left in buf\n", __FUNCTION__); - - memcpy(buffer, buffer + 1, bytesRead); - --bytesRead; - } - - *nbytes_read += bytesRead; - numBytesToRead -= bytesRead; - } - - if (tty_gemini_udp_format) - { - int *intSizedBuffer = (int *)geminiBuffer; - if (intSizedBuffer[0] != tty_sequence_number) - { - // Not the right reply just do the read again. - return tty_read_expanded(fd, buf, nbytes, timeout_seconds, timeout_microseconds, nbytes_read); - } - - *nbytes_read -= 8; - memcpy(buf, geminiBuffer + 8, *nbytes_read); - } - - return TTY_OK; - -#endif -} - -int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read) -{ - return tty_read_section_expanded(fd, buf, stop_char, (long)timeout, (long)0, nbytes_read); -} - -int tty_read_section_expanded(int fd, char *buf, char stop_char, long timeout_seconds, long timeout_microseconds, int *nbytes_read) -{ -#ifdef _WIN32 - - if (fd == -1) - return TTY_ERRNO; - - DWORD bytesRead = 0; - int err = TTY_OK; - *nbytes_read = 0; - - char readBuffer[257] = {0}; - - if (tty_debug) - IDLog("%s: Request to read until stop char '%#02X' with %ld s %ld us timeout for fd %d\n", __FUNCTION__, stop_char, timeout_seconds, timeout_microseconds, fd); - - if (tty_gemini_udp_format) - { - if ((err = tty_timeout_microseconds(fd, timeout_seconds, timeout_microseconds))) - return err; - - if (!ReadFile((HANDLE)_get_osfhandle(fd), readBuffer, 255, &bytesRead, NULL)) - return TTY_READ_ERROR; - - if (bytesRead < 0) - return TTY_READ_ERROR; - - int *intSizedBuffer = (int *)readBuffer; - if (intSizedBuffer[0] != tty_sequence_number) - { - // Not the right reply just do the read again. - return tty_read_section_expanded(fd, buf, stop_char, timeout_seconds, timeout_microseconds, nbytes_read); - } - - for (int index = 8; static_cast(index) < static_cast(bytesRead); index++) - { - (*nbytes_read)++; - - if (*(readBuffer + index) == stop_char) - { - strncpy(buf, readBuffer + 8, *nbytes_read); - return TTY_OK; - } - } - } - else if (tty_generic_udp_format) - { - if ((err = tty_timeout_microseconds(fd, timeout_seconds, timeout_microseconds))) - return err; - - if (!ReadFile((HANDLE)_get_osfhandle(fd), readBuffer, 255, &bytesRead, NULL)) - return TTY_READ_ERROR; - - if (bytesRead < 0) - return TTY_READ_ERROR; - - for (int index = 0; static_cast(index) < static_cast(bytesRead); index++) - { - (*nbytes_read)++; - - if (*(readBuffer + index) == stop_char) - { - strncpy(buf, readBuffer, *nbytes_read); - return TTY_OK; - } - } - } - else - { - uint8_t *read_char = 0; - - for (;;) - { - if ((err = tty_timeout_microseconds(fd, timeout_seconds, timeout_microseconds))) - return err; - - read_char = (uint8_t *)(buf + *nbytes_read); - - if (!ReadFile((HANDLE)_get_osfhandle(fd), read_char, 1, &bytesRead, NULL)) - return TTY_READ_ERROR; - - if (bytesRead < 0) - return TTY_READ_ERROR; - - if (tty_debug) - IDLog("%s: buffer[%d]=%#X (%c)\n", __FUNCTION__, (*nbytes_read), *read_char, *read_char); - - if (!(tty_clear_trailing_lf && *read_char == 0X0A && *nbytes_read == 0)) - (*nbytes_read)++; - else - { - if (tty_debug) - IDLog("%s: Cleared LF char left in buf\n", __FUNCTION__); - } - - if (*read_char == stop_char) - { - return TTY_OK; - } - } - } - - return TTY_TIME_OUT; - -#else - - if (fd == -1) - return TTY_ERRNO; - - char readBuffer[257] = {0}; - - int bytesRead = 0; - int err = TTY_OK; - *nbytes_read = 0; - - uint8_t *read_char = 0; - - if (tty_debug) - IDLog("%s: Request to read until stop char '%#02X' with %ld s %ld us timeout for fd %d\n", __FUNCTION__, stop_char, timeout_seconds, timeout_microseconds, fd); - - if (tty_gemini_udp_format) - { - bytesRead = read(fd, readBuffer, 255); - - if (bytesRead < 0) - return TTY_READ_ERROR; - - int *intSizedBuffer = (int *)readBuffer; - if (intSizedBuffer[0] != tty_sequence_number) - { - // Not the right reply just do the read again. - return tty_read_section_expanded(fd, buf, stop_char, timeout_seconds, timeout_microseconds, nbytes_read); - } - - for (int index = 8; index < bytesRead; index++) - { - (*nbytes_read)++; - - if (*(readBuffer + index) == stop_char) - { - strncpy(buf, readBuffer + 8, *nbytes_read); - return TTY_OK; - } - } - } - else if (tty_generic_udp_format) - { - bytesRead = read(fd, readBuffer, 255); - if (bytesRead < 0) - return TTY_READ_ERROR; - for (int index = 0; index < bytesRead; index++) - { - (*nbytes_read)++; - - if (*(readBuffer + index) == stop_char) - { - strncpy(buf, readBuffer, *nbytes_read); - return TTY_OK; - } - } - } - else - { - for (;;) - { - if ((err = tty_timeout_microseconds(fd, timeout_seconds, timeout_microseconds))) - return err; - - read_char = (uint8_t *)(buf + *nbytes_read); - bytesRead = read(fd, read_char, 1); - - if (bytesRead < 0) - return TTY_READ_ERROR; - - if (tty_debug) - IDLog("%s: buffer[%d]=%#X (%c)\n", __FUNCTION__, (*nbytes_read), *read_char, *read_char); - - if (!(tty_clear_trailing_lf && *read_char == 0X0A && *nbytes_read == 0)) - (*nbytes_read)++; - else - { - if (tty_debug) - IDLog("%s: Cleared LF char left in buf\n", __FUNCTION__); - } - - if (*read_char == stop_char) - { - return TTY_OK; - } - } - } - - return TTY_TIME_OUT; - -#endif -} - -int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read) -{ -#ifdef _WIN32 - if (fd == -1) - return TTY_ERRNO; - - DWORD bytesRead = 0; - int err = TTY_OK; - *nbytes_read = 0; - uint8_t *read_char = 0; - - if (tty_gemini_udp_format || tty_generic_udp_format) - return tty_read_section(fd, buf, stop_char, timeout, nbytes_read); - - if (tty_debug) - IDLog("%s: Request to read until stop char '%#02X' with %d timeout for fd %d\n", __FUNCTION__, stop_char, timeout, fd); - - memset(buf, 0, nsize); - - for (;;) - { - if ((err = tty_timeout(fd, timeout))) - return err; - - read_char = (uint8_t *)(buf + *nbytes_read); - - if (!ReadFile((HANDLE)_get_osfhandle(fd), read_char, 1, &bytesRead, NULL)) - return TTY_READ_ERROR; - - if (bytesRead < 0) - return TTY_READ_ERROR; - - if (tty_debug) - IDLog("%s: buffer[%d]=%#X (%c)\n", __FUNCTION__, (*nbytes_read), *read_char, *read_char); - - if (!(tty_clear_trailing_lf && *read_char == 0X0A && *nbytes_read == 0)) - (*nbytes_read)++; - else - { - if (tty_debug) - IDLog("%s: Cleared LF char left in buf\n", __FUNCTION__); - } - - if (*read_char == stop_char) - return TTY_OK; - else if (*nbytes_read >= nsize) - return TTY_OVERFLOW; - } - -#else - if (fd == -1) - return TTY_ERRNO; - - if (tty_gemini_udp_format || tty_generic_udp_format) - return tty_read_section(fd, buf, stop_char, timeout, nbytes_read); - - int bytesRead = 0; - int err = TTY_OK; - *nbytes_read = 0; - uint8_t *read_char = 0; - - if (tty_debug) - IDLog("%s: Request to read until stop char '%#02X' with %d timeout for fd %d\n", __FUNCTION__, stop_char, timeout, fd); - - memset(buf, 0, nsize); - - for (;;) - { - if ((err = tty_timeout(fd, timeout))) - return err; - - read_char = (uint8_t *)(buf + *nbytes_read); - bytesRead = read(fd, read_char, 1); - - if (bytesRead < 0) - return TTY_READ_ERROR; - - if (tty_debug) - IDLog("%s: buffer[%d]=%#X (%c)\n", __FUNCTION__, (*nbytes_read), *read_char, *read_char); - - if (!(tty_clear_trailing_lf && *read_char == 0X0A && *nbytes_read == 0)) - (*nbytes_read)++; - else - { - if (tty_debug) - IDLog("%s: Cleared LF char left in buf\n", __FUNCTION__); - } - - if (*read_char == stop_char) - return TTY_OK; - else if (*nbytes_read >= nsize) - return TTY_OVERFLOW; - } -#endif -} - -#if defined(BSD) && !defined(__GNU__) -// BSD - OSX version -int tty_connect(const char *device, int bit_rate, int word_size, int parity, int stop_bits, int *fd) -{ - int t_fd = -1; - int bps; - char msg[80]; - int handshake; - struct termios tty_setting; - - // Open the serial port read/write, with no controlling terminal, and don't wait for a connection. - // The O_NONBLOCK flag also causes subsequent I/O on the device to be non-blocking. - // See open(2) ("man 2 open") for details. - - t_fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK); - if (t_fd == -1) - { - IDLog("Error opening serial port (%s) - %s(%d).\n", device, strerror(errno), errno); - goto error; - } - - // Note that open() follows POSIX semantics: multiple open() calls to the same file will succeed - // unless the TIOCEXCL ioctl is issued. This will prevent additional opens except by root-owned - // processes. - // See tty(4) ("man 4 tty") and ioctl(2) ("man 2 ioctl") for details. - - if (ioctl(t_fd, TIOCEXCL) == -1) - { - IDLog("Error setting TIOCEXCL on %s - %s(%d).\n", device, strerror(errno), errno); - goto error; - } - - // Now that the device is open, clear the O_NONBLOCK flag so subsequent I/O will block. - // See fcntl(2) ("man 2 fcntl") for details. - - if (fcntl(t_fd, F_SETFL, 0) == -1) - { - IDLog("Error clearing O_NONBLOCK %s - %s(%d).\n", device, strerror(errno), errno); - goto error; - } - - // Get the current options and save them so we can restore the default settings later. - if (tcgetattr(t_fd, &tty_setting) == -1) - { - IDLog("Error getting tty attributes %s - %s(%d).\n", device, strerror(errno), errno); - goto error; - } - - // Set raw input (non-canonical) mode, with reads blocking until either a single character - // has been received or a one second timeout expires. - // See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios") for details. - - cfmakeraw(&tty_setting); - tty_setting.c_cc[VMIN] = 1; - tty_setting.c_cc[VTIME] = 10; - - // The baud rate, word length, and handshake options can be set as follows: - switch (bit_rate) - { - case 0: - bps = B0; - break; - case 50: - bps = B50; - break; - case 75: - bps = B75; - break; - case 110: - bps = B110; - break; - case 134: - bps = B134; - break; - case 150: - bps = B150; - break; - case 200: - bps = B200; - break; - case 300: - bps = B300; - break; - case 600: - bps = B600; - break; - case 1200: - bps = B1200; - break; - case 1800: - bps = B1800; - break; - case 2400: - bps = B2400; - break; - case 4800: - bps = B4800; - break; - case 9600: - bps = B9600; - break; - case 19200: - bps = B19200; - break; - case 38400: - bps = B38400; - break; - case 57600: - bps = B57600; - break; - case 115200: - bps = B115200; - break; - case 230400: - bps = B230400; - break; -#if !defined(__APPLE__) && !defined(__FreeBSD__) - case 460800: - bps = B460800; - break; - case 576000: - bps = B576000; - break; - case 921600: - bps = B921600; - break; -#endif - default: - if (snprintf(msg, sizeof(msg), "tty_connect: %d is not a valid bit rate.", bit_rate) < 0) - perror(NULL); - else - perror(msg); - return TTY_PARAM_ERROR; - } - - cfsetspeed(&tty_setting, bps); // Set baud rate - /* word size */ - switch (word_size) - { - case 5: - tty_setting.c_cflag |= CS5; - break; - case 6: - tty_setting.c_cflag |= CS6; - break; - case 7: - tty_setting.c_cflag |= CS7; - break; - case 8: - tty_setting.c_cflag |= CS8; - break; - default: - if (snprintf(msg, sizeof(msg), "tty_connect: %d is not a valid data bit count.", word_size) < 0) - perror(NULL); - else - perror(msg); - - return TTY_PARAM_ERROR; - } - - /* parity */ - switch (parity) - { - case PARITY_NONE: - break; - case PARITY_EVEN: - tty_setting.c_cflag |= PARENB; - break; - case PARITY_ODD: - tty_setting.c_cflag |= PARENB | PARODD; - break; - default: - if (snprintf(msg, sizeof(msg), "tty_connect: %d is not a valid parity selection value.", parity) < 0) - perror(NULL); - else - perror(msg); - - return TTY_PARAM_ERROR; - } - - /* stop_bits */ - switch (stop_bits) - { - case 1: - break; - case 2: - tty_setting.c_cflag |= CSTOPB; - break; - default: - if (snprintf(msg, sizeof(msg), "tty_connect: %d is not a valid stop bit count.", stop_bits) < 0) - perror(NULL); - else - perror(msg); - - return TTY_PARAM_ERROR; - } - -#if defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) - // Starting with Tiger, the IOSSIOSPEED ioctl can be used to set arbitrary baud rates - // other than those specified by POSIX. The driver for the underlying serial hardware - // ultimately determines which baud rates can be used. This ioctl sets both the input - // and output speed. - - speed_t speed = 14400; // Set 14400 baud - if (ioctl(t_fd, IOSSIOSPEED, &speed) == -1) - { - IDLog("Error calling ioctl(..., IOSSIOSPEED, ...) - %s(%d).\n", strerror(errno), errno); - } -#endif - - // Cause the new options to take effect immediately. - if (tcsetattr(t_fd, TCSANOW, &tty_setting) == -1) - { - IDLog("Error setting tty attributes %s - %s(%d).\n", device, strerror(errno), errno); - goto error; - } - - // To set the modem handshake lines, use the following ioctls. - // See tty(4) ("man 4 tty") and ioctl(2) ("man 2 ioctl") for details. - - if (ioctl(t_fd, TIOCSDTR) == -1) // Assert Data Terminal Ready (DTR) - { - IDLog("Error asserting DTR %s - %s(%d).\n", device, strerror(errno), errno); - } - - if (ioctl(t_fd, TIOCCDTR) == -1) // Clear Data Terminal Ready (DTR) - { - IDLog("Error clearing DTR %s - %s(%d).\n", device, strerror(errno), errno); - } - - handshake = TIOCM_DTR | TIOCM_RTS | TIOCM_CTS | TIOCM_DSR; - if (ioctl(t_fd, TIOCMSET, &handshake) == -1) - // Set the modem lines depending on the bits set in handshake - { - IDLog("Error setting handshake lines %s - %s(%d).\n", device, strerror(errno), errno); - } - - // To read the state of the modem lines, use the following ioctl. - // See tty(4) ("man 4 tty") and ioctl(2) ("man 2 ioctl") for details. - - if (ioctl(t_fd, TIOCMGET, &handshake) == -1) - // Store the state of the modem lines in handshake - { - IDLog("Error getting handshake lines %s - %s(%d).\n", device, strerror(errno), errno); - } - - IDLog("Handshake lines currently set to %d\n", handshake); - -#if defined(MAC_OS_X_VERSION_10_3) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3) - unsigned long mics = 1UL; - - // Set the receive latency in microseconds. Serial drivers use this value to determine how often to - // dequeue characters received by the hardware. Most applications don't need to set this value: if an - // app reads lines of characters, the app can't do anything until the line termination character has been - // received anyway. The most common applications which are sensitive to read latency are MIDI and IrDA - // applications. - - if (ioctl(t_fd, IOSSDATALAT, &mics) == -1) - { - // set latency to 1 microsecond - IDLog("Error setting read latency %s - %s(%d).\n", device, strerror(errno), errno); - goto error; - } -#endif - - *fd = t_fd; - /* return success */ - return TTY_OK; - - // Failure path -error: - if (t_fd != -1) - { - close(t_fd); - *fd = -1; - } - - return TTY_PORT_FAILURE; -} -#else -int tty_connect(const char *device, int bit_rate, int word_size, int parity, int stop_bits, int *fd) -{ -#ifdef _WIN32 - HANDLE hComm; // 声明一个HANDLE类型的变量hComm - // 使用&操作符获取hComm的地址 - hComm = CreateFile(device, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); - if (hComm == INVALID_HANDLE_VALUE) // 检查hComm是否等于INVALID_HANDLE_VALUE - { - printf("Failed to open serial port.\n"); - return TTY_PORT_FAILURE; - } - - DCB dcbSerialParams = {0}; - dcbSerialParams.DCBlength = sizeof(dcbSerialParams); - if (!GetCommState(hComm, &dcbSerialParams)) - { - printf("Failed to get serial port state.\n"); - CloseHandle(hComm); - return TTY_PORT_FAILURE; - } - - // 设置波特率 - switch (bit_rate) - { - case 9600: - dcbSerialParams.BaudRate = CBR_9600; - break; - case 19200: - dcbSerialParams.BaudRate = CBR_19200; - break; - case 38400: - dcbSerialParams.BaudRate = CBR_38400; - break; - case 57600: - dcbSerialParams.BaudRate = CBR_57600; - break; - case 115200: - dcbSerialParams.BaudRate = CBR_115200; - break; - default: - printf("Invalid bit rate.\n"); - CloseHandle(hComm); - return TTY_PORT_FAILURE; - } - - // 设置数据位、校验位和停止位 - dcbSerialParams.ByteSize = word_size; - switch (parity) - { - case 0: - dcbSerialParams.Parity = NOPARITY; - break; - case 1: - dcbSerialParams.Parity = EVENPARITY; - break; - case 2: - dcbSerialParams.Parity = ODDPARITY; - break; - default: - printf("Invalid parity.\n"); - CloseHandle(hComm); - return TTY_PORT_FAILURE; - } - if (stop_bits == 1) - dcbSerialParams.StopBits = ONESTOPBIT; - else if (stop_bits == 2) - dcbSerialParams.StopBits = TWOSTOPBITS; - else - { - printf("Invalid stop bits.\n"); - CloseHandle(hComm); - return TTY_PORT_FAILURE; - } - - if (!SetCommState(hComm, &dcbSerialParams)) - { - printf("Failed to set serial port state.\n"); - CloseHandle(hComm); - return TTY_PORT_FAILURE; - } - - COMMTIMEOUTS timeouts = {0}; - timeouts.ReadIntervalTimeout = 1000; - timeouts.ReadTotalTimeoutConstant = 1000; - timeouts.ReadTotalTimeoutMultiplier = 1000; - timeouts.WriteTotalTimeoutConstant = 1000; - timeouts.WriteTotalTimeoutMultiplier = 1000; - if (!SetCommTimeouts(hComm, &timeouts)) - { - printf("Failed to set serial port timeouts.\n"); - CloseHandle(hComm); - return TTY_PORT_FAILURE; - } - - *fd = (int)(intptr_t)hComm; - - return TTY_OK; - -#else - int t_fd = -1; - int i = 0; - char msg[128] = {0}; - int bps; - struct termios tty_setting; - // Check for bluetooth & virtualcom which can be shared - int ignore_exclusive_close = strstr(device, "rfcomm") || strstr(device, "Bluetooth") || strstr(device, "virtualcom"); - - // Open as Read/Write, no fnctl, and close on exclusive - for (i = 0; i < 3; i++) - { - // Do not use O_CLOEXEC when ignored - t_fd = open(device, O_RDWR | O_NOCTTY | (ignore_exclusive_close ? 0 : O_CLOEXEC)); - if (t_fd > 0) - break; - else - { - *fd = -1; - if (errno == EBUSY) - { - usleep(1e6); - continue; - } - else - return TTY_PORT_FAILURE; - } - } - - if (t_fd == -1) - return TTY_PORT_BUSY; - -#if !defined(__CYGWIN__) - // Set port in exclusive mode to prevent other non-root processes from opening it. - // JM 2019-08-12: Do not set it when ignored - if (ignore_exclusive_close == 0 && ioctl(t_fd, TIOCEXCL) == -1) - { - perror("tty_connect: Error setting TIOCEXC."); - close(t_fd); - return TTY_PORT_FAILURE; - } -#endif - - // Get the current options and save them so we can restore the default settings later. - if (tcgetattr(t_fd, &tty_setting) == -1) - { - perror("tty_connect: failed getting tty attributes."); - close(t_fd); - return TTY_PORT_FAILURE; - } - - /* Control Modes - Set bps rate */ - switch (bit_rate) - { - case 0: - bps = B0; - break; - case 50: - bps = B50; - break; - case 75: - bps = B75; - break; - case 110: - bps = B110; - break; - case 134: - bps = B134; - break; - case 150: - bps = B150; - break; - case 200: - bps = B200; - break; - case 300: - bps = B300; - break; - case 600: - bps = B600; - break; - case 1200: - bps = B1200; - break; - case 1800: - bps = B1800; - break; - case 2400: - bps = B2400; - break; - case 4800: - bps = B4800; - break; - case 9600: - bps = B9600; - break; - case 19200: - bps = B19200; - break; - case 38400: - bps = B38400; - break; - case 57600: - bps = B57600; - break; - case 115200: - bps = B115200; - break; - case 230400: - bps = B230400; - break; - case 460800: - bps = B460800; - break; - case 576000: - bps = B576000; - break; - case 921600: - bps = B921600; - break; - default: - if (snprintf(msg, sizeof(msg), "tty_connect: %d is not a valid bit rate.", bit_rate) < 0) - perror(NULL); - else - perror(msg); - close(t_fd); - return TTY_PARAM_ERROR; - } - if ((cfsetispeed(&tty_setting, bps) < 0) || (cfsetospeed(&tty_setting, bps) < 0)) - { - perror("tty_connect: failed setting bit rate."); - close(t_fd); - return TTY_PORT_FAILURE; - } - - /* Control Modes - set no flow control word size, parity and stop bits. - Also don't hangup automatically and ignore modem status. - Finally enable receiving characters. */ - tty_setting.c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD | HUPCL | CRTSCTS); - tty_setting.c_cflag |= (CLOCAL | CREAD); - - /* word size */ - switch (word_size) - { - case 5: - tty_setting.c_cflag |= CS5; - break; - case 6: - tty_setting.c_cflag |= CS6; - break; - case 7: - tty_setting.c_cflag |= CS7; - break; - case 8: - tty_setting.c_cflag |= CS8; - break; - default: - - fprintf(stderr, "Default\n"); - if (snprintf(msg, sizeof(msg), "tty_connect: %d is not a valid data bit count.", word_size) < 0) - perror(NULL); - else - perror(msg); - - close(t_fd); - return TTY_PARAM_ERROR; - } - - /* parity */ - switch (parity) - { - case PARITY_NONE: - break; - case PARITY_EVEN: - tty_setting.c_cflag |= PARENB; - break; - case PARITY_ODD: - tty_setting.c_cflag |= PARENB | PARODD; - break; - default: - - fprintf(stderr, "Default1\n"); - if (snprintf(msg, sizeof(msg), "tty_connect: %d is not a valid parity selection value.", parity) < 0) - perror(NULL); - else - perror(msg); - - close(t_fd); - return TTY_PARAM_ERROR; - } - - /* stop_bits */ - switch (stop_bits) - { - case 1: - break; - case 2: - tty_setting.c_cflag |= CSTOPB; - break; - default: - fprintf(stderr, "Default2\n"); - if (snprintf(msg, sizeof(msg), "tty_connect: %d is not a valid stop bit count.", stop_bits) < 0) - perror(NULL); - else - perror(msg); - - close(t_fd); - return TTY_PARAM_ERROR; - } - /* Control Modes complete */ - - /* Ignore bytes with parity errors and make terminal raw and dumb.*/ - tty_setting.c_iflag &= ~(PARMRK | ISTRIP | IGNCR | ICRNL | INLCR | IXOFF | IXON | IXANY); - tty_setting.c_iflag |= INPCK | IGNPAR | IGNBRK; - - /* Raw output.*/ - tty_setting.c_oflag &= ~(OPOST | ONLCR); - - /* Local Modes - Don't echo characters. Don't generate signals. - Don't process any characters.*/ - tty_setting.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG | IEXTEN | NOFLSH | TOSTOP); - tty_setting.c_lflag |= NOFLSH; - - /* blocking read until 1 char arrives */ - tty_setting.c_cc[VMIN] = 1; - tty_setting.c_cc[VTIME] = 0; - - /* now clear input and output buffers and activate the new terminal settings */ - tcflush(t_fd, TCIOFLUSH); - if (tcsetattr(t_fd, TCSANOW, &tty_setting)) - { - perror("tty_connect: failed setting attributes on serial port."); - tty_disconnect(t_fd); - return TTY_PORT_FAILURE; - } - - *fd = t_fd; - /* return success */ - return TTY_OK; -#endif -} -// Unix - Linux version - -#endif - -int tty_disconnect(int fd) -{ - if (fd == -1) - return TTY_ERRNO; - -#ifdef _WIN32 - HANDLE hComm = (HANDLE)_get_osfhandle(fd); - - if (!CloseHandle(hComm)) - return TTY_ERRNO; -#else - int err; - - tcflush(fd, TCIOFLUSH); - err = close(fd); - - if (err != 0) - return TTY_ERRNO; -#endif - - return TTY_OK; -} - -void tty_error_msg(int err_code, char *err_msg, int err_msg_len) -{ - switch (err_code) - { - case TTY_OK: - snprintf(err_msg, err_msg_len, "No Error"); - break; - - case TTY_READ_ERROR: - snprintf(err_msg, err_msg_len, "Read Error: %s", strerror(errno)); - break; - - case TTY_WRITE_ERROR: - snprintf(err_msg, err_msg_len, "Write Error: %s", strerror(errno)); - break; - - case TTY_SELECT_ERROR: - snprintf(err_msg, err_msg_len, "Select Error: %s", strerror(errno)); - break; - - case TTY_TIME_OUT: - snprintf(err_msg, err_msg_len, "Timeout error"); - break; - - case TTY_PORT_FAILURE: - if (errno == EACCES) - snprintf(err_msg, err_msg_len, - "Port failure Error: %s. Try adding your user to the dialout group and restart (sudo adduser " - "$USER dialout)", - strerror(errno)); - else - snprintf(err_msg, err_msg_len, "Port failure Error: %s. Check if device is connected to this port.", - strerror(errno)); - - break; - - case TTY_PARAM_ERROR: - snprintf(err_msg, err_msg_len, "Parameter error"); - break; - - case TTY_ERRNO: - snprintf(err_msg, err_msg_len, "%s", strerror(errno)); - break; - - case TTY_OVERFLOW: - snprintf(err_msg, err_msg_len, "Read overflow"); - break; - - case TTY_PORT_BUSY: - snprintf(err_msg, err_msg_len, "Port is busy"); - break; - - default: - snprintf(err_msg, err_msg_len, "Error: unrecognized error code"); - break; - } -} - -double rangeHA(double r) -{ - double res = r; - while (res < -12.0) - res += 24.0; - while (res >= 12.0) - res -= 24.0; - return res; -} - -double range24(double r) -{ - double res = r; - while (res < 0.0) - res += 24.0; - while (res > 24.0) - res -= 24.0; - return res; -} - -double range360(double r) -{ - double res = r; - while (res < 0.0) - res += 360.0; - while (res > 360.0) - res -= 360.0; - return res; -} - -double rangeDec(double decdegrees) -{ - if ((decdegrees >= 270.0) && (decdegrees <= 360.0)) - return (decdegrees - 360.0); - if ((decdegrees >= 180.0) && (decdegrees < 270.0)) - return (180.0 - decdegrees); - if ((decdegrees >= 90.0) && (decdegrees < 180.0)) - return (180.0 - decdegrees); - return decdegrees; -} - -#if defined(HAVE_LIBNOVA) -double get_local_sidereal_time(double longitude) -{ - return range24(ln_get_apparent_sidereal_time(ln_get_julian_from_sys()) + longitude / 15.0); -} - -void get_hrz_from_equ(struct ln_equ_posn *object, struct ln_lnlat_posn *observer, double JD, struct ln_hrz_posn *position) -{ - ln_get_hrz_from_equ(object, observer, JD, position); - position->az -= 180; - if (position->az < 0) - position->az += 360; -} - -void get_equ_from_hrz(struct ln_hrz_posn *object, struct ln_lnlat_posn *observer, double JD, struct ln_equ_posn *position) -{ - struct ln_hrz_posn libnova_object; - libnova_object.az = object->az + 180; - if (libnova_object.az > 360) - libnova_object.az -= 360; - libnova_object.alt = object->alt; - - ln_get_equ_from_hrz(&libnova_object, observer, JD, position); -} - -#endif // HAVE_LIBNOVA - -double get_local_hour_angle(double sideral_time, double ra) -{ - double HA = sideral_time - ra; - return rangeHA(HA); -} - -void get_alt_az_coordinates(double Ha, double Dec, double Lat, double *Alt, double *Az) -{ - double alt, az; - Ha *= M_PI / 180.0; - Dec *= M_PI / 180.0; - Lat *= M_PI / 180.0; - alt = asin(sin(Dec) * sin(Lat) + cos(Dec) * cos(Lat) * cos(Ha)); - az = acos((sin(Dec) - sin(alt) * sin(Lat)) / (cos(alt) * cos(Lat))); - alt *= 180.0 / M_PI; - az *= 180.0 / M_PI; - if (sin(Ha) >= 0.0) - az = 360 - az; - *Alt = alt; - *Az = az; -} - -double estimate_geocentric_elevation(double Lat, double El) -{ - Lat *= M_PI / 180.0; - Lat = sin(Lat); - El += Lat * (EARTHRADIUSPOLAR - EARTHRADIUSEQUATORIAL); - return El; -} - -double estimate_field_rotation_rate(double Alt, double Az, double Lat) -{ - Alt *= M_PI / 180.0; - Az *= M_PI / 180.0; - Lat *= M_PI / 180.0; - double ret = cos(Lat) * cos(Az) / cos(Alt); - ret *= 180.0 / M_PI; - return ret; -} - -double estimate_field_rotation(double HA, double rate) -{ - HA *= rate; - while (HA >= 360.0) - HA -= 360.0; - while (HA < 0) - HA += 360.0; - return HA; -} - -double as2rad(double as) -{ - return as * M_PI / (60.0 * 60.0 * 12.0); -} - -double rad2as(double rad) -{ - return rad * (60.0 * 60.0 * 12.0) / M_PI; -} - -double estimate_distance(double parsecs, double parallax_radius) -{ - return parallax_radius / sin(as2rad(parsecs)); -} - -double m2au(double m) -{ - return m / ASTRONOMICALUNIT; -} - -double calc_delta_magnitude(double mag_ratio, double *spectrum, double *ref_spectrum, int spectrum_size) -{ - double delta_mag = 0; - for (int l = 0; l < spectrum_size; l++) - { - delta_mag += spectrum[l] * mag_ratio * ref_spectrum[l] / spectrum[l]; - } - delta_mag /= spectrum_size; - return delta_mag; -} - -double calc_star_mass(double delta_mag, double ref_size) -{ - return delta_mag * ref_size; -} - -double estimate_orbit_radius(double obs_lambda, double ref_lambda, double period) -{ - return M_PI * 2 * DOPPLER(REDSHIFT(obs_lambda, ref_lambda), LIGHTSPEED) / period; -} - -double estimate_secondary_mass(double star_mass, double star_drift, double orbit_radius) -{ - return orbit_radius * pow(star_drift * orbit_radius, 3) * 3 * star_mass; -} - -double estimate_secondary_size(double star_size, double dropoff_ratio) -{ - return pow(dropoff_ratio * pow(star_size, 2), 0.5); -} - -double calc_photon_flux(double rel_magnitude, double filter_bandwidth, double wavelength, double steradian) -{ - return pow(10, rel_magnitude * -0.4) * (LUMEN(wavelength) * steradian * filter_bandwidth); -} - -double calc_rel_magnitude(double photon_flux, double filter_bandwidth, double wavelength, double steradian) -{ - return pow(10, 1.0 / (photon_flux / (LUMEN(wavelength) * steradian * filter_bandwidth))) / -0.4; -} - -double estimate_absolute_magnitude(double delta_dist, double delta_mag) -{ - return sqrt(delta_dist) * delta_mag; -} - -void baseline_2d_projection(double alt, double az, double baseline[3], double wavelength, double uvresult[2]) -{ - az *= M_PI / 180.0; - alt *= M_PI / 180.0; - uvresult[0] = (baseline[0] * sin(az) + baseline[1] * cos(az)); - uvresult[1] = (baseline[1] * sin(alt) * sin(az) - baseline[0] * sin(alt) * cos(az) + baseline[2] * cos(alt)); - uvresult[0] *= AIRY / wavelength; - uvresult[1] *= AIRY / wavelength; -} - -double baseline_delay(double alt, double az, double baseline[3]) -{ - az *= M_PI / 180.0; - alt *= M_PI / 180.0; - return cos(az) * baseline[1] * cos(alt) - baseline[0] * sin(az) * cos(alt) + sin(alt) * baseline[2]; -} - -#if defined(_MSC_VER) -#undef snprintf -#pragma warning(pop) -#endif - -#ifdef _WIN32 -HANDLE convertToHandle(int PortFd) -{ - HANDLE hSerial = (HANDLE)_get_osfhandle(PortFd); - if (hSerial == INVALID_HANDLE_VALUE) - { - return NULL; - } - DCB dcbSerialParams = {0}; - dcbSerialParams.DCBlength = sizeof(dcbSerialParams); - - if (!GetCommState(hSerial, &dcbSerialParams)) - { - return NULL; - } - return hSerial; -} -#endif diff --git a/src/core/base/hydrogencom.hpp b/src/core/base/hydrogencom.hpp deleted file mode 100644 index 20edfef5..00000000 --- a/src/core/base/hydrogencom.hpp +++ /dev/null @@ -1,542 +0,0 @@ -/** HYDROGEN LIB - * Common routines used by all drivers - * Copyright (C) 2003 by Jason Harris (jharris@30doradus.org) - * Elwood C. Downey - * Jasem Mutlaq - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -/** \file hydrogencom.h - * \brief Implementations for common driver routines. - * - * The HYDROGEN Common Routine Library provides formatting and serial routines employed by many HYDROGEN drivers. Currently, the library is composed of the following sections: - * - *
          - *
        • Formatting Functions
        • - *
        • Conversion Functions
        • - *
        • TTY Functions
        • - *
        - * - * \author Jason Harris - * \author Elwood C. Downey - * \author Jasem Mutlaq - */ - -#pragma once - -#if defined(_MSC_VER) -#define _USE_MATH_DEFINES -#endif - -#include - -#ifndef J2000 -#define J2000 2451545.0 -#endif -#define ERRMSG_SIZE 1024 - -#ifndef STELLAR_DAY -#define STELLAR_DAY 86164.098903691 -#endif -#ifndef TRACKRATE_SIDEREAL -#define TRACKRATE_SIDEREAL ((360.0 * 3600.0) / STELLAR_DAY) -#endif -#ifndef SOLAR_DAY -#define SOLAR_DAY 86400 -#endif -#ifndef TRACKRATE_SOLAR -#define TRACKRATE_SOLAR ((360.0 * 3600.0) / SOLAR_DAY) -#endif -#ifndef TRACKRATE_LUNAR -#define TRACKRATE_LUNAR 14.511415 -#endif -#ifndef EARTHRADIUSEQUATORIAL -#define EARTHRADIUSEQUATORIAL 6378137.0 -#endif -#ifndef EARTHRADIUSPOLAR -#define EARTHRADIUSPOLAR 6356752.0 -#endif -#ifndef EARTHRADIUSMEAN -#define EARTHRADIUSMEAN 6372797.0 -#endif -#ifndef SUNMASS -#define SUNMASS 1.98847E+30 -#endif -#ifndef PLANK_H -#define PLANK_H 6.62607015E-34 -#endif -#ifndef DIRAC_H -#define DIRAC_H (PLANK_H / (2 * M_PI)) -#endif -#ifndef EINSTEIN_G -#define EINSTEIN_G 6.67408E-11 -#endif -#ifndef EULER -#define EULER 2.71828182845904523536028747135266249775724709369995 -#endif -#ifndef ROOT2 -#define ROOT2 1.41421356237309504880168872420969807856967187537694 -#endif -#ifndef AIRY -#define AIRY 1.21966 -#endif -#ifndef CIRCLE_DEG -#define CIRCLE_DEG 360 -#endif -#ifndef CIRCLE_AM -#define CIRCLE_AM (CIRCLE_DEG * 60) -#endif -#ifndef CIRCLE_AS -#define CIRCLE_AS (CIRCLE_AM * 60) -#endif -#ifndef RAD_AS -#define RAD_AS (CIRCLE_AS / (M_PI * 2)) -#endif -#ifndef ASTRONOMICALUNIT -#define ASTRONOMICALUNIT 1.495978707E+11 -#endif -#ifndef PARSEC -#define PARSEC (ASTRONOMICALUNIT * RAD_AS) -#endif -#ifndef LIGHTSPEED -#define LIGHTSPEED 299792458.0 -#endif -#ifndef JULIAN_LY -#define JULIAN_LY (LIGHTSPEED * SOLAR_DAY * 365) -#endif -#ifndef STELLAR_LY -#define STELLAR_LY (LIGHTSPEED * STELLAR_DAY * 365) -#endif -#ifndef FLUX -#define FLUX(wavelength) (wavelength / (PLANK_H * LIGHTSPEED)) -#endif -#ifndef CANDLE -#define CANDLE ((1.0 / 683.0) * FLUX(555)) -#endif -#ifndef LUMEN -#define LUMEN(wavelength) (CANDLE / (4 * M_PI) * pow((FLUX(wavelength) / FLUX(555)), 0.25)) -#endif -#ifndef REDSHIFT -#define REDSHIFT(wavelength, reference) (1.0 - (reference / wavelength)) -#endif -#ifndef DOPPLER -#define DOPPLER(shift, speed) (speed * shift) -#endif - -extern const char *Direction[]; -extern const char *SolarSystem[]; - -struct ln_date; -struct ln_equ_posn; -struct ln_lnlat_posn; -struct ln_hrz_posn; - -/* TTY Error Codes */ -enum TTY_ERROR -{ - TTY_OK = 0, - TTY_READ_ERROR = -1, - TTY_WRITE_ERROR = -2, - TTY_SELECT_ERROR = -3, - TTY_TIME_OUT = -4, - TTY_PORT_FAILURE = -5, - TTY_PARAM_ERROR = -6, - TTY_ERRNO = -7, - TTY_OVERFLOW = -8, - TTY_PORT_BUSY = -9, -}; - -#ifdef __cplusplus -extern "C" -{ -#endif - - /** - * \defgroup ttyFunctions TTY Functions: Functions to perform common terminal access routines. - */ - - /* @{ */ - - /** \brief read buffer from terminal - * \param fd file descriptor - * \param buf pointer to store data. Must be initialized and big enough to hold data. - * \param nbytes number of bytes to read. - * \param timeout number of seconds to wait for terminal before a timeout error is issued. - * \param nbytes_read the number of bytes read. - * \return On success, it returns TTY_OK, otherwise, a TTY_ERROR code. - */ - int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read); - - /** \brief read buffer from terminal with a delimiter - * \param fd file descriptor - * \param buf pointer to store data. Must be initialized and big enough to hold data. - * \param stop_char if the function encounters \e stop_char then it stops reading and returns the buffer. - * \param timeout_seconds number of seconds to wait for terminal before a timeout error is issued. - * - * (Total time = timeout_seconds + timeout_microseconds) - * \param timeout_microseconds number of microseconds to wait for terminal before a timeout error is issued. - * - * (Total time = timeout_seconds + timeout_microseconds) - * \param nbytes_read the number of bytes read. - * \return On success, it returns TTY_OK, otherwise, a TTY_ERROR code. - */ - int tty_read_expanded(int fd, char *buf, int nbytes, long timeout_seconds, long timeout_microseconds, int *nbytes_read); - - /** \brief read buffer from terminal with a delimiter - * \param fd file descriptor - * \param buf pointer to store data. Must be initialized and big enough to hold data. - * \param stop_char if the function encounters \e stop_char then it stops reading and returns the buffer. - * \param timeout number of seconds to wait for terminal before a timeout error is issued. - * \param nbytes_read the number of bytes read. - * \return On success, it returns TTY_OK, otherwise, a TTY_ERROR code. - */ - int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read); - - /** \brief read buffer from terminal with a delimiter - * \param fd file descriptor - * \param buf pointer to store data. Must be initialized and big enough to hold data. - * \param stop_char if the function encounters \e stop_char then it stops reading and returns the buffer. - * \param nsize size of buf. If stop character is not encountered before nsize, the function aborts. - * \param timeout_seconds number of seconds to wait for terminal before a timeout error is issued. - * - * (Total Timeout is timeout_seconds + timeout_microseconds) - * \param timeout_microseconds number of microseconds to wait for terminal before a timeout error is issued. - * - * (Total Timeout is timeout_seconds + timeout_microseconds) - * \param nbytes_read the number of bytes read. - * \return On success, it returns TTY_OK, otherwise, a TTY_ERROR code. - */ - int tty_read_section_expanded(int fd, char *buf, char stop_char, long timeout_seconds, long timeout_microseconds, - int *nbytes_read); - - /** \brief read buffer from terminal with a delimiter - * \param fd file descriptor - * \param buf pointer to store data. Must be initialized and big enough to hold data. - * \param stop_char if the function encounters \e stop_char then it stops reading and returns the buffer. - * \param nsize size of buf. If stop character is not encountered before nsize, the function aborts. - * \param timeout number of seconds to wait for terminal before a timeout error is issued. - * \param nbytes_read the number of bytes read. - * \return On success, it returns TTY_OK, otherwise, a TTY_ERROR code. - */ - int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read); - - /** \brief Writes a buffer to fd. - * \param fd file descriptor - * \param buffer a null-terminated buffer to write to fd. - * \param nbytes number of bytes to write from \e buffer - * \param nbytes_written the number of bytes written - * \return On success, it returns TTY_OK, otherwise, a TTY_ERROR code. - */ - int tty_write(int fd, const char *buffer, int nbytes, int *nbytes_written); - - /** \brief Writes a null terminated string to fd. - * \param fd file descriptor - * \param buffer the buffer to write to fd. - * \param nbytes_written the number of bytes written - * \return On success, it returns TTY_OK, otherwise, a TTY_ERROR code. - */ - int tty_write_string(int fd, const char *buffer, int *nbytes_written); - - /** \brief Establishes a tty connection to a terminal device. - * \param device the device node. e.g. /dev/ttyS0 - * \param bit_rate bit rate - * \param word_size number of data bits, 7 or 8, USE 8 DATA BITS with modbus - * \param parity 0=no parity, 1=parity EVEN, 2=parity ODD - * \param stop_bits number of stop bits : 1 or 2 - * \param fd \e fd is set to the file descriptor value on success. - * \return On success, it returns TTY_OK, otherwise, a TTY_ERROR code. - * \author Wildi Markus - */ - int tty_connect(const char *device, int bit_rate, int word_size, int parity, int stop_bits, int *fd); - - /** \brief Closes a tty connection and flushes the bus. - * \param fd the file descriptor to close. - * \return On success, it returns TTY_OK, otherwise, a TTY_ERROR code. - */ - int tty_disconnect(int fd); - - /** \brief Retrieve the tty error message - * \param err_code the error code return by any TTY function. - * \param err_msg an initialized buffer to hold the error message. - * \param err_msg_len length in bytes of \e err_msg - */ - void tty_error_msg(int err_code, char *err_msg, int err_msg_len); - - /** \brief tty_set_debug Enable or disable debug which prints verbose information. - * \param debug 1 to enable, 0 to disable - */ - void tty_set_debug(int debug); - void tty_set_gemini_udp_format(int enabled); - void tty_set_generic_udp_format(int enabled); - void tty_clr_trailing_read_lf(int enabled); - - int tty_timeout(int fd, int timeout); - - int tty_timeout_microseconds(int fd, long timeout_seconds, long timeout_microseconds); - - /* @} */ - - /** - * \defgroup convertFunctions Formatting Functions: Functions to perform handy formatting and conversion routines. - */ - - /* @{ */ - - /** \brief Converts a sexagesimal number to a string. - * sprint the variable a in sexagesimal format into out[]. - * \param out a pointer to store the sexagesimal number. - * \param a the sexagesimal number to convert. - * \param w the number of spaces in the whole part. - * \param fracbase is the number of pieces a whole is to broken into; valid options:\n - * \li 360000: \:mm:ss.ss - * \li 36000: \:mm:ss.s - * \li 3600: \:mm:ss - * \li 600: \:mm.m - * \li 60: \:mm - * \return number of characters written to out, not counting final null terminator. - */ - int fs_sexa(char *out, double a, int w, int fracbase); - - /** \brief convert sexagesimal string str AxBxC to double. - * x can be anything non-numeric. Any missing A, B or C will be assumed 0. Optional - and + can be anywhere. - * \param str0 string containing sexagesimal number. - * \param dp pointer to a double to store the sexagesimal number. - * \return return 0 if ok, -1 if can't find a thing. - */ - int f_scansexa(const char *str0, double *dp); - -/** \brief Extract ISO 8601 time and store it in a tm struct. - * \param timestr a string containing date and time in ISO 8601 format. - * \param iso_date a pointer to a \e ln_date structure to store the extracted time and date (libnova). - * \return 0 on success, -1 on failure. - */ - int extractISOTime(const char *timestr, struct ln_date *iso_date); - - void getSexComponents(double value, int *d, int *m, int *s); - void getSexComponentsIID(double value, int *d, int *m, double *s); - - /** \brief Fill buffer with properly formatted INumber string. - * \param buf to store the formatted string. - * \param format format in sprintf style. - * \param value the number to format. - * \return length of string. - * \note buf must be of length MAXHYDROGENFORMAT at minimum - */ - int numberFormat(char *buf, const char *format, double value); - - /** \brief Get a unix timestamp with nanosecond precision - * \return Seconds since UNIX Epoch. - */ - double time_ns(); - - /** \brief Create an ISO 8601 formatted time stamp. The format is YYYY-MM-DDTHH:MM:SS - * \return The formatted time stamp. - */ - const char *indi_timestamp(); - - /** \brief rangeHA Limits the hour angle value to be between -12 ---> 12 - * \param r current hour angle value - * \return Limited value (-12,12) - */ - double rangeHA(double r); - - /** \brief range24 Limits a number to be between 0-24 range. - * \param r number to be limited - * \return Limited number - */ - double range24(double r); - - /** \brief range360 Limits an angle to be between 0-360 degrees. - * \param r angle - * \return Limited angle - */ - double range360(double r); - - /** \brief rangeDec Limits declination value to be in -90 to 90 range. - * \param r declination angle - * \return limited declination - */ - double rangeDec(double r); - - /** \brief get_local_sidereal_time Returns local sideral time given longitude and system clock. - * \param longitude Longitude in HYDROGEN format (0 to 360) increasing eastward. - * \return Local Sidereal Time. - */ - double get_local_sidereal_time(double longitude); - - /** \brief get_local_hour_angle Returns local hour angle of an object - * \param local_sideral_time Local Sideral Time - * \param ra RA of object - * \return Hour angle in hours (-12 to 12) - */ - double get_local_hour_angle(double local_sideral_time, double ra); - - /** \brief get_hrz_from_equ Calculate horizontal coordinates from equatorial coordinates. - * \param object Equatorial Object Coordinates - * \param observer Observer Location - * \param JD Julian Date - * \param position Calculated Horizontal Coordinates. - * \note Use this instead of libnova ln_get_hrz_from_equ since it corrects libnova Azimuth (0 = North and not South). - */ - void get_hrz_from_equ(struct ln_equ_posn *object, struct ln_lnlat_posn *observer, double JD, struct ln_hrz_posn *position); - - /** \brief ln_get_equ_from_hrz Calculate Equatorial EOD Coordinates from horizontal coordinates - * \param object Horizontal Object Coordinates - * \param observer Observer Location - * \param JD Julian Date - * \param position Calculated Equatorial Coordinates. - * \note Use this instead of libnova ln_get_equ_from_hrz since it corrects libnova Azimuth (0 = North and not South). - */ - void get_equ_from_hrz(struct ln_hrz_posn *object, struct ln_lnlat_posn *observer, double JD, - struct ln_equ_posn *position); - /** \brief get_alt_az_coordinates Returns alt-azimuth coordinates of an object - * \param hour_angle Hour angle in hours (-12 to 12) - * \param dec DEC of object - * \param latitude latitude in HYDROGEN format (-90 to +90) - * \param alt ALT of object will be returned here - * \param az AZ of object will be returned here - */ - void get_alt_az_coordinates(double hour_angle, double dec, double latitude, double *alt, double *az); - - /** \brief estimate_geocentric_elevation Returns an estimation of the actual geocentric elevation - * \param latitude latitude in HYDROGEN format (-90 to +90) - * \param sea_level_elevation sea level elevation - * \return Aproximated geocentric elevation - */ - double estimate_geocentric_elevation(double latitude, double sea_level_elevation); - - /** \brief estimate_field_rotation_rate Returns an estimation of the field rotation rate of the object - * \param Alt altitude coordinate of the object - * \param Az azimuth coordinate of the object - * \param latitude latitude in HYDROGEN format (-90 to +90) - * \return Aproximation of the field rotation rate - */ - double estimate_field_rotation_rate(double Alt, double Az, double latitude); - - /** \brief estimate_field_rotation Returns an estimation of the field rotation rate of the object - * \param hour_angle Hour angle in hours (-12 to 12) - * \param field_rotation_rate the field rotation rate - * \return Aproximation of the absolute field rotation - */ - double estimate_field_rotation(double hour_angle, double field_rotation_rate); - - /** \brief as2rad Convert arcseconds into radians - * \param as the arcseconds to convert - * \return radians corresponding as angle value - */ - double as2rad(double as); - - /** \brief rad2as Convert radians into arcseconds - * \param as the radians to convert - * \return arcseconds corresponding as angle value - */ - double rad2as(double rad); - - /** \brief estimate_distance Convert parallax arcseconds into meters - * \param parsec the parallax arcseconds to convert - * \return Estimation of the distance in measure units used in parallax_radius - */ - double estimate_distance(double parsecs, double parallax_radius); - - /** @brief m2au Convert meters into astronomical units - * @param m the distance in meters to convert - * @return Estimation of the distance in astronomical units - */ - double m2au(double m); - - /** \brief calc_delta_magnitude Returns the difference of magnitudes given two spectra - * \param mag_ratio Reference magnitude - * \param spectrum The spectrum of the star under exam - * \param ref_spectrum The spectrum of the reference star - * \param spectrum_size The size of the spectrum array in elements - * \return the magnitude difference - */ - double calc_delta_magnitude(double mag_ratio, double *spectrum, double *ref_spectrum, int spectrum_size); - - /** \brief calc_photon_flux Returns the photon flux of the object with the given magnitude observed at a determined wavelenght using a passband filter through a steradian expressed cone - * \param rel_magnitude Relative magnitude of the object observed - * \param filter_bandwidth Filter bandwidth in meters - * \param wavelength Wavelength in meters - * \param steradian The light cone in steradians - * \return the photon flux in Lumen - */ - double calc_photon_flux(double rel_magnitude, double filter_bandwidth, double wavelength, double steradian); - - /** \brief calc_rel_magnitude Returns the relative magnitude of the object with the given photon flux measured at a determined wavelenght using a passband filter over an incident surface - * \param photon_flux The photon flux in Lumen - * \param filter_bandwidth Filter bandwidth in meters - * \param wavelength Wavelength in meters - * \param incident_surface The incident surface in square meters - * \return the relative magnitude of the object observed - */ - double calc_rel_magnitude(double photon_flux, double filter_bandwidth, double wavelength, double steradian); - - /** \brief estimate_absolute_magnitude Returns an estimation of the absolute magnitude of an object given its distance and the difference of its magnitude with a reference object - * \param dist The distance in parallax radiuses - * \param delta_mag The difference of magnitudes - * \return Aproximation of the absolute magnitude in Δmag - */ - double estimate_absolute_magnitude(double dist, double delta_mag); - - /** \brief estimate the star mass in ref_size units e.g. sun masses or kgs - * \param delta_mag The absolute magnitude ratio between the reference object used as unit in ref_size. - * \param ref_mass The mass of the reference object used to calculate the magnitude difference. - */ - double estimate_star_mass(double delta_mag, double ref_mass); - - /** \brief estimate the orbit radius of an object with known mass orbiting around a star. - * \param obs_lambda The observed wavelength of a spectral line observed on the star affected by redshift or blueshift. - * \param ref_lambda The reference wavelength of the spectral line observed on earth or the nullshift spectral line position. - * \param period The orbital period. - */ - double estimate_orbit_radius(double obs_lambda, double ref_lambda, double period); - - /** \brief estimate the mass of an object with known mass orbiting around a star. - * \param star_mass The mass of the star hosting an orbiting object. - * \param star_drift The star lagrange point L1 (observed drift of the star). - * \param orbit_radius The estimated orbit radius of the companion object (star, planet, cloud). - */ - double estimate_secondary_mass(double star_mass, double star_drift, double orbit_radius); - - /** \brief estimate the size of an object occulting a star in star_size units. - * \param star_size The size of the occulted star. - * \param dropoff_ratio The light curve dropoff during the transit. Linear scale. 0.0=no dropoff 1.0=totally occulted. - */ - double estimate_secondary_size(double star_size, double dropoff_ratio); - - /** \brief baseline_2d_projection Returns the coordinates of the projection of a single baseline targeting the object by coordinates - * \param alt current altitude of the target. - * \param az azimuth position of the target. - * \param baseline the baseline in meters. Three-dimensional xyz north is z axis y is UTC0 x is UTC0+90°. - * \param wavelength The observing electromagnetic wavelength, the lower the size increases. - * \param uvresult result plane coordinates of the current projection given the baseline and target vector. - */ - void baseline_2d_projection(double alt, double az, double baseline[3], double wavelength, double uvresult[2]); - - /** \brief baseline_delay Returns the delay in meters of a single baseline targeting the object by coordinates - * \param alt current altitude of the target. - * \param az azimuth position of the target. - * \param baseline the baseline in meters. Three-dimensional xyz north is z axis y is UTC0 x is UTC0+90°. - * \param wavelength The observing electromagnetic wavelength, the lower the size increases. - * \return double[2] UV plane coordinates of the current projection given the baseline and target vector. - */ - double baseline_delay(double alt, double az, double baseline[3]); - - /* @} */ - -#ifdef __cplusplus -} -#endif diff --git a/src/core/base/hydrogendevapi.cpp b/src/core/base/hydrogendevapi.cpp deleted file mode 100644 index c43ecd08..00000000 --- a/src/core/base/hydrogendevapi.cpp +++ /dev/null @@ -1,907 +0,0 @@ -#include "hydrogendevapi.hpp" -#include "hydrogencom.hpp" -#include "locale/locale_compat.hpp" -#include "base64.hpp" -#include "userio.hpp" -#include "hydrogenuserio.hpp" -#include "hydrogenutility.hpp" - -#include -#include -#include -#include -#include -#ifdef _WIN32 -#include -#include -#endif - -/** \section IUSave */ - -void IUSaveConfigNumber(FILE *fp, const INumberVectorProperty *nvp) -{ - IUUserIONewNumber(userio_file(), fp, nvp); -} - -void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp) -{ - IUUserIONewText(userio_file(), fp, tvp); -} - -void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp) -{ - IUUserIONewSwitchFull(userio_file(), fp, svp); -} - -void IUSaveConfigBLOB(FILE *fp, const IBLOBVectorProperty *bvp) -{ - IUUserIONewBLOB(userio_file(), fp, bvp); -} - -/* save malloced copy of newtext in tp->text, reusing if not first time */ -void IUSaveText(IText *tp, const char *newtext) -{ - /* copy in fresh string */ - size_t size = strlen(newtext) + 1; - tp->text = (char *)realloc((void *)tp->text, size); - memcpy(tp->text, newtext, size); -} - -int IUSaveBLOB(IBLOB *bp, int size, int blobsize, char *blob, char *format) -{ - bp->bloblen = blobsize; - bp->size = size; - bp->blob = blob; - indi_strlcpy(bp->format, format, MAXHYDROGENFORMAT); - return 0; -} - -/** Get configuration root XML pointer - * N.B. Must be freed by the caller */ -XMLEle *configRootFP(const char *device) -{ - static const int MAXRBUF = 2048; - char configFileName[MAXRBUF]; - char configDir[MAXRBUF]; - char errmsg[MAXRBUF]; - struct stat st; - FILE *fp = NULL; - - snprintf(configDir, MAXRBUF, "%s/.indi/", getenv("HOME")); - - if (getenv("HYDROGENCONFIG")) - strncpy(configFileName, getenv("HYDROGENCONFIG"), MAXRBUF); - else - { - int len = snprintf(configFileName, sizeof(configFileName), "%s%s_config.xml", configDir, device); - if (len >= sizeof(configFileName)) { - } - /* - int len = snprintf(configFileName, MAXRBUF, "%s%s_config.xml", configDir, device); - if (len >= MAXRBUF) - { - } - */ - } - - if (stat(configDir, &st) != 0) - { -#ifdef _WIN32 - if (mkdir(configDir) != 0) -#else - if (mkdir(configDir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0) -#endif - return NULL; - } - - stat(configFileName, &st); -#ifdef _WIN32 - BOOL isAdmin = FALSE; - SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; - PSID AdministratorsGroup; - if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup)) - { - if (CheckTokenMembership(NULL, AdministratorsGroup, &isAdmin) == FALSE) - isAdmin = FALSE; - FreeSid(AdministratorsGroup); - } - if ((st.st_uid == 0 && st.st_mode & S_IFDIR) || (st.st_gid == 0 && isAdmin == TRUE)) - return NULL; -#else - /* If file is owned by root and current user is NOT root then abort */ - if ((st.st_uid == 0 && getuid() != 0) || (st.st_gid == 0 && getgid() != 0)) - return NULL; -#endif - fp = fopen(configFileName, "r"); - if (fp == NULL) - return NULL; - - LilXML *lp = newLilXML(); - - XMLEle *root = readXMLFile(fp, lp, errmsg); - - delLilXML(lp); - fclose(fp); - - return root; -} -/** \section IULoad */ - -int IULoadConfigNumber(const INumberVectorProperty *nvp) -{ - char errmsg[1024]; - char *rdev, *rname; - int foundCounter = 0; - XMLEle *root = configRootFP(nvp->device); - if (root == NULL) - return -1; - - XMLEle *ep = NULL; - - for (ep = nextXMLEle(root, 1); ep != NULL; ep = nextXMLEle(root, 0)) - { - /* pull out device and name */ - if (crackDN(ep, &rdev, &rname, errmsg) < 0) - { - delXMLEle(root); - return -1; - } - - // It doesn't belong to our device?? - if (strcmp(nvp->device, rdev)) - continue; - - if (!strcmp(nvp->name, rname)) - { - XMLEle *element = NULL; - for (element = nextXMLEle(ep, 1); element != NULL; element = nextXMLEle(ep, 0)) - { - INumber *member = IUFindNumber(nvp, findXMLAttValu(element, "name")); - if (member) - { - member->value = atof(pcdataXMLEle(element)); - foundCounter++; - } - } - break; - } - } - - delXMLEle(root); - return foundCounter; -} - -int IULoadConfigText(const ITextVectorProperty *tvp) -{ - char errmsg[1024]; - char *rdev, *rname; - int foundCounter = 0; - XMLEle *root = configRootFP(tvp->device); - if (root == NULL) - return -1; - - XMLEle *ep = NULL; - - for (ep = nextXMLEle(root, 1); ep != NULL; ep = nextXMLEle(root, 0)) - { - /* pull out device and name */ - if (crackDN(ep, &rdev, &rname, errmsg) < 0) - { - delXMLEle(root); - return -1; - } - - // It doesn't belong to our device?? - if (strcmp(tvp->device, rdev)) - continue; - - if (!strcmp(tvp->name, rname)) - { - XMLEle *element = NULL; - for (element = nextXMLEle(ep, 1); element != NULL; element = nextXMLEle(ep, 0)) - { - IText *member = IUFindText(tvp, findXMLAttValu(element, "name")); - if (member) - { - IUSaveText(member, pcdataXMLEle(element)); - foundCounter++; - } - } - break; - } - } - - delXMLEle(root); - return foundCounter; -} - -int IULoadConfigSwitch(const ISwitchVectorProperty *svp) -{ - char errmsg[1024]; - char *rdev, *rname; - int foundCounter = 0; - XMLEle *root = configRootFP(svp->device); - if (root == NULL) - return -1; - - XMLEle *ep = NULL; - - for (ep = nextXMLEle(root, 1); ep != NULL; ep = nextXMLEle(root, 0)) - { - /* pull out device and name */ - if (crackDN(ep, &rdev, &rname, errmsg) < 0) - { - delXMLEle(root); - return -1; - } - - // It doesn't belong to our device?? - if (strcmp(svp->device, rdev)) - continue; - - if (!strcmp(svp->name, rname)) - { - XMLEle *element = NULL; - for (element = nextXMLEle(ep, 1); element != NULL; element = nextXMLEle(ep, 0)) - { - ISwitch *member = IUFindSwitch(svp, findXMLAttValu(element, "name")); - if (member) - { - ISState state; - if (crackISState(pcdataXMLEle(element), &state) == 0) - { - member->s = state; - foundCounter++; - } - } - } - break; - } - } - - delXMLEle(root); - return foundCounter; -} - -/** \section IUFind */ - -/* find a member of an IText vector, else NULL */ -IText *IUFindText(const ITextVectorProperty *tvp, const char *name) -{ - for (int i = 0; i < tvp->ntp; i++) - if (strcmp(tvp->tp[i].name, name) == 0) - return (&tvp->tp[i]); - fprintf(stderr, "No IText '%s' in %s.%s\n", name, tvp->device, tvp->name); - return (NULL); -} - -/* find a member of an INumber vector, else NULL */ -INumber *IUFindNumber(const INumberVectorProperty *nvp, const char *name) -{ - for (int i = 0; i < nvp->nnp; i++) - if (strcmp(nvp->np[i].name, name) == 0) - return (&nvp->np[i]); - fprintf(stderr, "No INumber '%s' in %s.%s\n", name, nvp->device, nvp->name); - return (NULL); -} - -/* find a member of an ISwitch vector, else NULL */ -ISwitch *IUFindSwitch(const ISwitchVectorProperty *svp, const char *name) -{ - for (int i = 0; i < svp->nsp; i++) - if (strcmp(svp->sp[i].name, name) == 0) - return (&svp->sp[i]); - fprintf(stderr, "No ISwitch '%s' in %s.%s\n", name, svp->device, svp->name); - return (NULL); -} - -/* find a member of an ILight vector, else NULL */ -ILight *IUFindLight(const ILightVectorProperty *lvp, const char *name) -{ - for (int i = 0; i < lvp->nlp; i++) - if (strcmp(lvp->lp[i].name, name) == 0) - return (&lvp->lp[i]); - fprintf(stderr, "No ILight '%s' in %s.%s\n", name, lvp->device, lvp->name); - return (NULL); -} - -/* find a member of an IBLOB vector, else NULL */ -IBLOB *IUFindBLOB(const IBLOBVectorProperty *bvp, const char *name) -{ - for (int i = 0; i < bvp->nbp; i++) - if (strcmp(bvp->bp[i].name, name) == 0) - return (&bvp->bp[i]); - fprintf(stderr, "No IBLOB '%s' in %s.%s\n", name, bvp->device, bvp->name); - return (NULL); -} - -/* find an ON member of an ISwitch vector, else NULL. - * N.B. user must make sense of result with ISRule in mind. - */ -ISwitch *IUFindOnSwitch(const ISwitchVectorProperty *svp) -{ - for (int i = 0; i < svp->nsp; i++) - if (svp->sp[i].s == ISS_ON) - return (&svp->sp[i]); - /*fprintf(stderr, "No ISwitch On in %s.%s\n", svp->device, svp->name);*/ - return (NULL); -} - -int IUFindIndex(const char *needle, char **hay, unsigned int n) -{ - for (int i = 0; i < (int)n; i++) - { - if (!strcmp(hay[i], needle)) - return i; - } - return -1; -} - -/* Find index of the ON member of an ISwitchVectorProperty */ -int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp) -{ - for (int i = 0; i < svp->nsp; i++) - if (svp->sp[i].s == ISS_ON) - return i; - return -1; -} - -/* Find name the ON member in the given states and names */ -const char *IUFindOnSwitchName(ISState *states, char *names[], int n) -{ - for (int i = 0; i < n; i++) - if (states[i] == ISS_ON) - return names[i]; - return NULL; -} - -/** \section IUReset */ - -/* Set all switches to off */ -void IUResetSwitch(ISwitchVectorProperty *svp) -{ - for (int i = 0; i < svp->nsp; i++) - svp->sp[i].s = ISS_OFF; -} - -/** \section IUUpdate */ - -/** \section IUFill */ - -void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s) -{ - indi_strlcpy(sp->name, name, sizeof(sp->name)); - - indi_strlcpy(sp->label, label[0] ? label : name, sizeof(sp->label)); - - sp->s = s; - sp->svp = NULL; - sp->aux = NULL; -} - -void IUFillLight(ILight *lp, const char *name, const char *label, IPState s) -{ - indi_strlcpy(lp->name, name, sizeof(lp->name)); - - indi_strlcpy(lp->label, label[0] ? label : name, sizeof(lp->label)); - - lp->s = s; - lp->lvp = NULL; - lp->aux = NULL; -} - -void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, - double step, double value) -{ - indi_strlcpy(np->name, name, sizeof(np->name)); - - indi_strlcpy(np->label, label[0] ? label : name, sizeof(np->label)); - - indi_strlcpy(np->format, format, sizeof(np->format)); - - np->min = min; - np->max = max; - np->step = step; - np->value = value; - np->nvp = NULL; - np->aux0 = NULL; - np->aux1 = NULL; -} - -void IUFillText(IText *tp, const char *name, const char *label, const char *initialText) -{ - indi_strlcpy(tp->name, name, sizeof(tp->name)); - - indi_strlcpy(tp->label, label[0] ? label : name, sizeof(tp->label)); - - if (tp->text && tp->text[0]) - free(tp->text); - tp->text = NULL; - - tp->tvp = NULL; - tp->aux0 = NULL; - tp->aux1 = NULL; - - if (initialText && initialText[0]) - IUSaveText(tp, initialText); -} - -void IUFillBLOB(IBLOB *bp, const char *name, const char *label, const char *format) -{ - memset(bp, 0, sizeof(IBLOB)); - - indi_strlcpy(bp->name, name, sizeof(bp->name)); - - indi_strlcpy(bp->label, label[0] ? label : name, sizeof(bp->label)); - - indi_strlcpy(bp->format, format, sizeof(bp->format)); - - bp->blob = 0; - bp->bloblen = 0; - bp->size = 0; - bp->bvp = 0; - bp->aux0 = 0; - bp->aux1 = 0; - bp->aux2 = 0; -} - -void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, - const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s) -{ - indi_strlcpy(svp->device, dev, sizeof(svp->device)); - - indi_strlcpy(svp->name, name, sizeof(svp->name)); - - indi_strlcpy(svp->label, label[0] ? label : name, sizeof(svp->label)); - - indi_strlcpy(svp->group, group, sizeof(svp->group)); - svp->timestamp[0] = '\0'; - - svp->p = p; - svp->r = r; - svp->timeout = timeout; - svp->s = s; - svp->sp = sp; - svp->nsp = nsp; -} - -void IUFillLightVector(ILightVectorProperty *lvp, ILight *lp, int nlp, const char *dev, const char *name, - const char *label, const char *group, IPState s) -{ - indi_strlcpy(lvp->device, dev, sizeof(lvp->device)); - - indi_strlcpy(lvp->name, name, sizeof(lvp->name)); - - indi_strlcpy(lvp->label, label[0] ? label : name, sizeof(lvp->label)); - - indi_strlcpy(lvp->group, group, sizeof(lvp->group)); - lvp->timestamp[0] = '\0'; - - lvp->s = s; - lvp->lp = lp; - lvp->nlp = nlp; -} - -void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, - const char *label, const char *group, IPerm p, double timeout, IPState s) -{ - indi_strlcpy(nvp->device, dev, sizeof(nvp->device)); - - indi_strlcpy(nvp->name, name, sizeof(nvp->name)); - - indi_strlcpy(nvp->label, label[0] ? label : name, sizeof(nvp->label)); - - indi_strlcpy(nvp->group, group, sizeof(nvp->group)); - nvp->timestamp[0] = '\0'; - - nvp->p = p; - nvp->timeout = timeout; - nvp->s = s; - nvp->np = np; - nvp->nnp = nnp; -} - -void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, - const char *label, const char *group, IPerm p, double timeout, IPState s) -{ - indi_strlcpy(tvp->device, dev, sizeof(tvp->device)); - - indi_strlcpy(tvp->name, name, sizeof(tvp->name)); - - indi_strlcpy(tvp->label, label[0] ? label : name, sizeof(tvp->label)); - - indi_strlcpy(tvp->group, group, sizeof(tvp->group)); - tvp->timestamp[0] = '\0'; - - tvp->p = p; - tvp->timeout = timeout; - tvp->s = s; - tvp->tp = tp; - tvp->ntp = ntp; -} - -void IUFillBLOBVector(IBLOBVectorProperty *bvp, IBLOB *bp, int nbp, const char *dev, const char *name, - const char *label, const char *group, IPerm p, double timeout, IPState s) -{ - memset(bvp, 0, sizeof(IBLOBVectorProperty)); - indi_strlcpy(bvp->device, dev, sizeof(bvp->device)); - - indi_strlcpy(bvp->name, name, sizeof(bvp->name)); - - indi_strlcpy(bvp->label, label[0] ? label : name, sizeof(bvp->label)); - - indi_strlcpy(bvp->group, group, sizeof(bvp->group)); - bvp->timestamp[0] = '\0'; - - bvp->p = p; - bvp->timeout = timeout; - bvp->s = s; - bvp->bp = bp; - bvp->nbp = nbp; -} - -/** \section IUSnoop **/ - -/* crack the snooped driver setNumberVector or defNumberVector message into - * the given INumberVectorProperty. - * return 0 if type, device and name match and all members are present, else - * return -1 - */ -int IUSnoopNumber(XMLEle *root, INumberVectorProperty *nvp) -{ - char *dev, *name; - XMLEle *ep; - - /* check and crack type, device, name and state */ - if (strcmp(tagXMLEle(root) + 3, "NumberVector") || crackDN(root, &dev, &name, NULL) < 0) - return (-1); - if (strcmp(dev, nvp->device) || strcmp(name, nvp->name)) - return (-1); /* not this property */ - (void)crackIPState(findXMLAttValu(root, "state"), &nvp->s); - - /* match each INumber with a oneNumber */ - locale_char_t *orig = hydrogen_locale_C_numeric_push(); - for (int i = 0; i < nvp->nnp; i++) - { - for (ep = nextXMLEle(root, 1); ep; ep = nextXMLEle(root, 0)) - { - if (!strcmp(tagXMLEle(ep) + 3, "Number") && !strcmp(nvp->np[i].name, findXMLAttValu(ep, "name"))) - { - if (f_scansexa(pcdataXMLEle(ep), &nvp->np[i].value) < 0) - { - hydrogen_locale_C_numeric_pop(orig); - return (-1); /* bad number format */ - } - break; - } - } - if (!ep) - { - hydrogen_locale_C_numeric_pop(orig); - return (-1); /* element not found */ - } - } - hydrogen_locale_C_numeric_pop(orig); - - /* ok */ - return (0); -} - -/* crack the snooped driver setTextVector or defTextVector message into - * the given ITextVectorProperty. - * return 0 if type, device and name match and all members are present, else - * return -1 - */ -int IUSnoopText(XMLEle *root, ITextVectorProperty *tvp) -{ - char *dev, *name; - XMLEle *ep; - - /* check and crack type, device, name and state */ - if (strcmp(tagXMLEle(root) + 3, "TextVector") || crackDN(root, &dev, &name, NULL) < 0) - return (-1); - if (strcmp(dev, tvp->device) || strcmp(name, tvp->name)) - return (-1); /* not this property */ - (void)crackIPState(findXMLAttValu(root, "state"), &tvp->s); - - /* match each IText with a oneText */ - for (int i = 0; i < tvp->ntp; i++) - { - for (ep = nextXMLEle(root, 1); ep; ep = nextXMLEle(root, 0)) - { - if (!strcmp(tagXMLEle(ep) + 3, "Text") && !strcmp(tvp->tp[i].name, findXMLAttValu(ep, "name"))) - { - IUSaveText(&tvp->tp[i], pcdataXMLEle(ep)); - break; - } - } - if (!ep) - return (-1); /* element not found */ - } - - /* ok */ - return (0); -} - -/* crack the snooped driver setLightVector or defLightVector message into - * the given ILightVectorProperty. it is not necessary that all ILight names - * be found. - * return 0 if type, device and name match, else return -1. - */ -int IUSnoopLight(XMLEle *root, ILightVectorProperty *lvp) -{ - char *dev, *name; - XMLEle *ep; - - /* check and crack type, device, name and state */ - if (strcmp(tagXMLEle(root) + 3, "LightVector") || crackDN(root, &dev, &name, NULL) < 0) - return (-1); - if (strcmp(dev, lvp->device) || strcmp(name, lvp->name)) - return (-1); /* not this property */ - - (void)crackIPState(findXMLAttValu(root, "state"), &lvp->s); - - /* match each oneLight with one ILight */ - for (ep = nextXMLEle(root, 1); ep; ep = nextXMLEle(root, 0)) - { - if (!strcmp(tagXMLEle(ep) + 3, "Light")) - { - const char *name = findXMLAttValu(ep, "name"); - for (int i = 0; i < lvp->nlp; i++) - { - if (!strcmp(lvp->lp[i].name, name)) - { - if (crackIPState(pcdataXMLEle(ep), &lvp->lp[i].s) < 0) - { - return (-1); /* unrecognized state */ - } - break; - } - } - } - } - - /* ok */ - return (0); -} - -/* crack the snooped driver setSwitchVector or defSwitchVector message into the - * given ISwitchVectorProperty. it is not necessary that all ISwitch names be - * found. - * return 0 if type, device and name match, else return -1. - */ -int IUSnoopSwitch(XMLEle *root, ISwitchVectorProperty *svp) -{ - char *dev, *name; - XMLEle *ep; - - /* check and crack type, device, name and state */ - if (strcmp(tagXMLEle(root) + 3, "SwitchVector") || crackDN(root, &dev, &name, NULL) < 0) - return (-1); - if (strcmp(dev, svp->device) || strcmp(name, svp->name)) - return (-1); /* not this property */ - (void)crackIPState(findXMLAttValu(root, "state"), &svp->s); - - /* match each oneSwitch with one ISwitch */ - for (ep = nextXMLEle(root, 1); ep; ep = nextXMLEle(root, 0)) - { - if (!strcmp(tagXMLEle(ep) + 3, "Switch")) - { - const char *name = findXMLAttValu(ep, "name"); - for (int i = 0; i < svp->nsp; i++) - { - if (!strcmp(svp->sp[i].name, name)) - { - if (crackISState(pcdataXMLEle(ep), &svp->sp[i].s) < 0) - { - return (-1); /* unrecognized state */ - } - break; - } - } - } - } - - /* ok */ - return (0); -} - -/* crack the snooped driver setBLOBVector message into the given - * IBLOBVectorProperty. it is not necessary that all IBLOB names be found. - * return 0 if type, device and name match, else return -1. - * N.B. we assume any existing blob in bvp has been malloced, which we free - * and replace with a newly malloced blob if found. - */ -int IUSnoopBLOB(XMLEle *root, IBLOBVectorProperty *bvp) -{ - char *dev, *name; - XMLEle *ep; - - /* check and crack type, device, name and state */ - if (strcmp(tagXMLEle(root), "setBLOBVector") || crackDN(root, &dev, &name, NULL) < 0) - return (-1); - - if (strcmp(dev, bvp->device) || strcmp(name, bvp->name)) - return (-1); /* not this property */ - - crackIPState(findXMLAttValu(root, "state"), &bvp->s); - - for (ep = nextXMLEle(root, 1); ep; ep = nextXMLEle(root, 0)) - { - if (strcmp(tagXMLEle(ep), "oneBLOB") == 0) - { - XMLAtt *na = findXMLAtt(ep, "name"); - if (na == NULL) - return (-1); - - IBLOB *bp = IUFindBLOB(bvp, valuXMLAtt(na)); - - if (bp == NULL) - return (-1); - - XMLAtt *fa = findXMLAtt(ep, "format"); - XMLAtt *sa = findXMLAtt(ep, "size"); - if (fa && sa) - { - int base64datalen = pcdatalenXMLEle(ep); - assert_mem(bp->blob = realloc(bp->blob, 3 * base64datalen / 4)); - bp->bloblen = from64tobits_fast((char *)bp->blob, pcdataXMLEle(ep), base64datalen); - indi_strlcpy(bp->format, valuXMLAtt(fa), MAXHYDROGENFORMAT); - bp->size = atoi(valuXMLAtt(sa)); - } - } - } - - /* ok */ - return (0); -} - -/** \section `IS` */ - -/* Functions are implemented in defaultdevice.cpp */ - -/** \section other */ - -int crackDN(XMLEle *root, char **dev, char **name, char msg[]) -{ - XMLAtt *ap; - - ap = findXMLAtt(root, "device"); - if (!ap) - { - sprintf(msg, "%s requires 'device' attribute", tagXMLEle(root)); - return (-1); - } - *dev = valuXMLAtt(ap); - - ap = findXMLAtt(root, "name"); - if (!ap) - { - sprintf(msg, "%s requires 'name' attribute", tagXMLEle(root)); - return (-1); - } - *name = valuXMLAtt(ap); - - return (0); -} - -int crackIPState(const char *str, IPState *ip) -{ - if (!strcmp(str, "Idle")) - *ip = IPS_IDLE; - else if (!strncmp(str, "Ok", 2)) - *ip = IPS_OK; - else if (!strcmp(str, "Busy")) - *ip = IPS_BUSY; - else if (!strcmp(str, "Alert")) - *ip = IPS_ALERT; - else - return (-1); - return (0); -} - -int crackISState(const char *str, ISState *ip) -{ - if (!strncmp(str, "On", 2)) - *ip = ISS_ON; - else if (!strcmp(str, "Off")) - *ip = ISS_OFF; - else - return (-1); - return (0); -} - -int crackIPerm(const char *str, IPerm *ip) -{ - if (!strncmp(str, "rw", 2)) - *ip = IP_RW; - else if (!strncmp(str, "ro", 2)) - *ip = IP_RO; - else if (!strncmp(str, "wo", 2)) - *ip = IP_WO; - else - return (-1); - return (0); -} - -int crackISRule(const char *str, ISRule *ip) -{ - if (!strcmp(str, "OneOfMany")) - *ip = ISR_1OFMANY; - else if (!strcmp(str, "AtMostOne")) - *ip = ISR_ATMOST1; - else if (!strcmp(str, "AnyOfMany")) - *ip = ISR_NOFMANY; - else - return (-1); - return (0); -} - -const char *pstateStr(IPState s) -{ - switch (s) - { - case IPS_IDLE: - return "Idle"; - case IPS_OK: - return "Ok"; - case IPS_BUSY: - return "Busy"; - case IPS_ALERT: - return "Alert"; - default: - fprintf(stderr, "Impossible IPState %d\n", s); - return NULL; - } -} - -const char *sstateStr(ISState s) -{ - switch (s) - { - case ISS_ON: - return "On"; - case ISS_OFF: - return "Off"; - default: - fprintf(stderr, "Impossible ISState %d\n", s); - return NULL; - } -} - -const char *ruleStr(ISRule r) -{ - switch (r) - { - case ISR_1OFMANY: - return "OneOfMany"; - case ISR_ATMOST1: - return "AtMostOne"; - case ISR_NOFMANY: - return "AnyOfMany"; - default: - fprintf(stderr, "Impossible ISRule %d\n", r); - return NULL; - } -} - -const char *permStr(IPerm p) -{ - switch (p) - { - case IP_RO: - return "ro"; - case IP_WO: - return "wo"; - case IP_RW: - return "rw"; - default: - fprintf(stderr, "Impossible IPerm %d\n", p); - return NULL; - } -} - -void xmlv1() -{ - userio_xmlv1(userio_file(), stdout); -} diff --git a/src/core/base/hydrogendevapi.hpp b/src/core/base/hydrogendevapi.hpp deleted file mode 100644 index 4b9d54bd..00000000 --- a/src/core/base/hydrogendevapi.hpp +++ /dev/null @@ -1,830 +0,0 @@ -/** HYDROGEN - * Copyright (C) 2003 - 2006 Elwood C. Downey - * - * Modified by Jasem Mutlaq (2003 - 2015) - * - * This library is free software; - * you can redistribute it and / or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; - * without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; - * if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA - */ - -#pragma once - -/** @file hydrogendevapi.h - * @brief Interface to the reference HYDROGEN C API device implementation on the Device Driver side. - * - * @author Elwood C. Downey - * @author Jasem Mutlaq - * - * This file is divided into two main sections:\n - *
        1. Functions the HYDROGEN device driver framework defines which the Driver may - * call:
        2. - * - *
          • IDxxx functions to send messages to an HYDROGEN client. Please note that it is recommended to use the - * HYDROGEN Library wrapper functions instead of calling IDxxx directly. - *
              - *
            • IDMessage: Use @ref HYDROGEN::Logger "HYDROGEN Logging Framework" functions (e.g. LOG_DEBUG..etc) instead.
            • - *
            • IDDefXXX: use @ref HYDROGEN::DefaultDevice "HYDROGEN Default Device" defXXX functions intead.
            • - *
            - *
          • - *
          • IExxx functions to implement the event driven model.
          • - *
          • IUxxx functions to perform handy utility functions.
          - * - *
        3. Functions the HYDROGEN device driver framework calls which the Driver must - * define:
        4. - * - *
          • ISxxx to respond to messages from a Client.
        - * - *

        These functions are the interface to the HYDROGEN C-language Device Driver - * reference implementation library. Any driver that uses this interface is - * expected to \#include "hydrogendevapi.hpp" and to link with indidrivermain.o and - * eventloop.o. Indidevapi.h further includes hydrogenapi.h. The former contains - * the prototypes for the functions documented here, although many functions - * take arguments defined in the latter.

        - * - *

        These functions make it much easier to write a compliant HYDROGEN driver than - * starting from scratch, and also serve as a concrete - * example of the interactions an HYDROGEN driver, in any language, is expected to - * accommodate.

        - * - *

        The reference driver framework and the optimizations made within the reference - * indiserver both assume and require that one driver program implements exactly - * one logical HYDROGEN device.

        - * - *

        The functions in this framework fall into two broad categories. Some are - * functions that a driver must define because they are called by the reference - * framework; these functions begin with IS. The remaining functions are library - * utilities a driver may use to do important operations.

        - * - *

        A major point to realize is that an HYDROGEN driver built with this framework - * does not contain the C main() function. As soon as a driver begins executing, - * it listens on stdin for HYDROGEN messages. Only when a valid and appropriate - * message is received will it then call the driver via one of the IS functions. - * The driver is then expected to respond promptly by calling one of the ID - * library functions. It may also use any of the IU utility functions as desired - * to make processing a message easier.

        - * - *

        Rather separate from these IS, ID and IU functions are a collection of - * functions that utilize the notion of a callback. In a callback design, - * the driver registers a function with the framework to be called under - * certain circumstances. When said circumstances occur, the framework will - * call the callback function. The driver never calls these callbacks directly. - * These callback functions begin with IE. They can arrange for a callback - * function to be called under three kinds of circumstances: when a given file - * descriptor may be read without blocking (because either data is available - * or EOF has been encountered), when a given time interval has elapsed, or - * when the framework has nothing urgent to do. The callback functions for each - * circumstance must be written according to a well defined prototype since, - * after all, the framework must know how to call the callback correctlty.

        - * - */ - -#include "hydrogenapi.h" -#include "lilxml.hpp" - -/******************************************************************************* - ******************************************************************************* - * - * Functions the HYDROGEN device driver framework defines which the Driver may call - * - ******************************************************************************* - ******************************************************************************* - */ - -#ifdef __cplusplus -extern "C" -{ -#endif - -/** - * @defgroup d2cFunctions IDDef Functions: Functions drivers call to define their properties to clients. - * - *

        Each of the following functions creates the appropriate XML formatted - * HYDROGEN message from its arguments and writes it to stdout. From there, is it - * typically read by indiserver which then sends it to the clients that have - * expressed interest in messages from the Device indicated in the message.

        - * - *

        In addition to type-specific arguments, all end with a printf-style format - * string, and appropriate subsequent arguments, that form the @param msg - * attribute within the HYDROGEN message. If the format argument is NULL, no message - * attribute is included with the message. Note that a \e timestamp - * attribute is also always added automatically based on the clock on the - * computer on which this driver is running.

        - * - */ - -/* Handy readability macro to avoid unused variables warnings */ -#ifndef HYDROGEN_UNUSED -#define HYDROGEN_UNUSED(x) (void)x -#endif - -/* enable warnings for printf-style functions */ -#ifndef ATTRIBUTE_FORMAT_PRINTF -#ifdef __GNUC__ -#define ATTRIBUTE_FORMAT_PRINTF(A, B) __attribute__((format(printf, (A), (B)))) -#else -#define ATTRIBUTE_FORMAT_PRINTF(A, B) -#endif -#endif - - /* @{ */ - - /** @brief Tell client to create a text vector property. - * @param t pointer to the vector text property to be defined. - * @param msg message in printf style to send to the client. May be NULL. - */ - extern void IDDefText(const ITextVectorProperty *t, const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); - extern void IDDefTextVA(const ITextVectorProperty *t, const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(2, 0); - - /** @brief Tell client to create a number number property. - * @param n pointer to the vector number property to be defined. - * @param msg message in printf style to send to the client. May be NULL. - */ - extern void IDDefNumber(const INumberVectorProperty *n, const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); - extern void IDDefNumberVA(const INumberVectorProperty *n, const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(2, 0); - - /** @brief Tell client to create a switch vector property. - * @param s pointer to the vector switch property to be defined. - * @param msg message in printf style to send to the client. May be NULL. - */ - extern void IDDefSwitch(const ISwitchVectorProperty *s, const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); - extern void IDDefSwitchVA(const ISwitchVectorProperty *s, const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(2, 0); - - /** @brief Tell client to create a light vector property. - * @param l pointer to the vector light property to be defined. - * @param msg message in printf style to send to the client. May be NULL. - */ - extern void IDDefLight(const ILightVectorProperty *l, const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); - extern void IDDefLightVA(const ILightVectorProperty *l, const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(2, 0); - - /** @brief Tell client to create a BLOB vector property. - * @param b pointer to the vector BLOB property to be defined. - * @param msg message in printf style to send to the client. May be NULL. - */ - extern void IDDefBLOB(const IBLOBVectorProperty *b, const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); - extern void IDDefBLOBVA(const IBLOBVectorProperty *b, const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(2, 0); - - /* @} */ - - /** - * @defgroup d2cuFunctions IDSet Functions: Functions drivers call to tell clients of new values for existing properties. - */ - - /* @{ */ - - /** @brief Tell client to update an existing text vector property. - * @param t pointer to the vector text property. - * @param msg message in printf style to send to the client. May be NULL. - */ - extern void IDSetText(const ITextVectorProperty *t, const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); - extern void IDSetTextVA(const ITextVectorProperty *t, const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(2, 0); - - /** @brief Tell client to update an existing number vector property. - * @param n pointer to the vector number property. - * @param msg message in printf style to send to the client. May be NULL. - */ - extern void IDSetNumber(const INumberVectorProperty *n, const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); - extern void IDSetNumberVA(const INumberVectorProperty *n, const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(2, 0); - - /** @brief Tell client to update an existing switch vector property. - * @param s pointer to the vector switch property. - * @param msg message in printf style to send to the client. May be NULL. - */ - extern void IDSetSwitch(const ISwitchVectorProperty *s, const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); - extern void IDSetSwitchVA(const ISwitchVectorProperty *s, const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(2, 0); - - /** @brief Tell client to update an existing light vector property. - * @param l pointer to the vector light property. - * @param msg message in printf style to send to the client. May be NULL. - */ - extern void IDSetLight(const ILightVectorProperty *l, const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); - extern void IDSetLightVA(const ILightVectorProperty *l, const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(2, 0); - - /** @brief Tell client to update an existing BLOB vector property. - * @param b pointer to the vector BLOB property. - * @param msg message in printf style to send to the client. May be NULL. - */ - extern void IDSetBLOB(const IBLOBVectorProperty *b, const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); - extern void IDSetBLOBVA(const IBLOBVectorProperty *b, const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(2, 0); - - /* @} */ - - /** - * @defgroup d2duFunctions ID Functions: Functions to delete properties, and log messages locally or remotely. - */ - - /* @{ */ - - /** @brief Function Drivers call to send log messages to Clients. - * If dev is specified the Client shall associate the message with that device; if dev is NULL the Client shall treat the message as generic from no specific Device. - * @param dev device name - * @param msg message in printf style to send to the client. - */ - extern void IDMessage(const char *dev, const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); - extern void IDMessageVA(const char *dev, const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(2, 0); - - /** @brief Function Drivers call to inform Clients a Property is no longer available, or the entire device is gone if name is NULL. - * @param dev device name. If device name is NULL, the entire device will be deleted. - * @param name property name to be deleted. - * @param msg message in printf style to send to the client. - */ - extern void IDDelete(const char *dev, const char *name, const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(3, 4); - extern void IDDeleteVA(const char *dev, const char *name, const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(3, 0); - - /** @brief Function Drivers call to log a message locally. - * The message is not sent to any Clients. - * @param msg message in printf style to send to the client. - */ - extern void IDLog(const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF(1, 2); - extern void IDLogVA(const char *msg, va_list arg) ATTRIBUTE_FORMAT_PRINTF(1, 0); - - /* @} */ - - /** - * @defgroup snoopFunctions ISnoop Functions: Functions drivers call to snoop on other drivers. - */ - - /* @{ */ - - /** @typedef BLOBHandling - * @brief How drivers handle BLOBs incoming from snooping drivers - */ - typedef enum - { - B_NEVER = 0, /*!< Never receive BLOBs */ - B_ALSO, /*!< Receive BLOBs along with normal messages */ - B_ONLY /*!< ONLY receive BLOBs from drivers, ignore all other traffic */ - } BLOBHandling; - - /** @brief Function a Driver calls to snoop on another Device. Snooped messages will then arrive via ISSnoopDevice. - * @param snooped_device name of the device to snoop. - * @param snooped_property name of the snooped property in the device. - */ - extern void IDSnoopDevice(const char *snooped_device, const char *snooped_property); - - /** @brief Function a Driver calls to control whether they will receive BLOBs from snooped devices. - * @param snooped_device name of the device to snoop. - * @param snooped_property name of property to snoop. If NULL, then all BLOBs from the given device are snooped. - * @param bh How drivers handle BLOBs incoming from snooping drivers. - */ - extern void IDSnoopBLOBs(const char *snooped_device, const char *snooped_property, BLOBHandling bh); - - /* @} */ - - /** - * @defgroup deventFunctions IE Functions: Functions drivers call to register with the HYDROGEN event utilities. - * Callbacks are called when a read on a file descriptor will not block. Timers are called once after a specified interval. Workprocs are called when there is nothing else to do. The "Add" functions return a unique id for use with their corresponding "Rm" removal function. An arbitrary pointer may be specified when a function is registered which will be stored and forwarded unchanged when the function is later invoked. - */ - - /* @{ */ - - /* signature of a callback, timout caller and work procedure function */ - - /** @typedef IE_CBF - * @brief Signature of a callback. - */ - typedef void(IE_CBF)(int readfiledes, void *userpointer); - - /** @typedef IE_TCF - * @brief Signature of a timeout caller. - */ - typedef void(IE_TCF)(void *userpointer); - - /** @typedef IE_WPF - * @brief Signature of a work procedure function. - */ - typedef void(IE_WPF)(void *userpointer); - - /* functions to add and remove callbacks, timers and work procedures */ - - /** @brief Register a new callback, \e fp, to be called with \e userpointer as argument when \e readfiledes is ready. - * @param readfiledes file descriptor. - * @param fp a pointer to the callback function. - * @param userpointer a pointer to be passed to the callback function when called. - * @return a unique callback id for use with IERmCallback(). - */ - extern int IEAddCallback(int readfiledes, IE_CBF *fp, void *userpointer); - - /** @brief Remove a callback function. - * @param callbackid the callback ID returned from IEAddCallback() - */ - extern void IERmCallback(int callbackid); - - /** @brief Register a new single-shot timer function, \e fp, to be called with \e ud as argument after \e ms. - * @param millisecs timer period in milliseconds. - * @param fp a pointer to the callback function. - * @param userpointer a pointer to be passed to the callback function when called. - * @return a unique id for use with IERmTimer(). - */ - extern int IEAddTimer(int millisecs, IE_TCF *fp, void *userpointer); - - /** @brief Register a new periodic timer function, \e fp, to be called with \e ud as argument after \e ms. - * @param millisecs timer period in milliseconds. - * @param fp a pointer to the callback function. - * @param userpointer a pointer to be passed to the callback function when called. - * @return a unique id for use with IERmTimer(). - */ - extern int IEAddPeriodicTimer(int millisecs, IE_TCF *fp, void *userpointer); - - /** @brief Returns the timer's remaining value in milliseconds left until the timeout. - * @param timerid the timer callback ID returned from IEAddTimer() or IEAddPeriodicTimer() - * @return If the timer not exists, the returned value will be -1. - */ - extern int IERemainingTimer(int timerid); - - /** @brief Returns the timer's remaining value in nanoseconds left until the timeout. - * @param tid the timer callback ID returned from addTimer() or addPeriodicTimer() - * @return If the timer not exists, the returned value will be -1. - */ - extern int IENSecRemainingTimer(int tid); - - /** @brief Remove the timer with the given \e timerid, as returned from IEAddTimer() or IEAddPeriodicTimer(). - * @param timerid the timer callback ID returned from IEAddTimer() or IEAddPeriodicTimer(). - */ - extern void IERmTimer(int timerid); - - /** @brief Add a new work procedure, fp, to be called with ud when nothing else to do. - * @param fp a pointer to the work procedure callback function. - * @param userpointer a pointer to be passed to the callback function when called. - * @return a unique id for use with IERmWorkProc(). - */ - extern int IEAddWorkProc(IE_WPF *fp, void *userpointer); - - /** @brief Remove a work procedure. - * @param workprocid The unique ID for the work procedure to be removed. - */ - extern void IERmWorkProc(int workprocid); - - /** @brief wait in-line for a flag to set, presumably by another event function */ - extern int IEDeferLoop(int maxms, int *flagp); - extern int IEDeferLoop0(int maxms, int *flagp); - - /* @} */ - - /** @defgroup dutilFunctions IU Functions: Functions drivers call to perform handy utility routines. - *

        This section describes handy utility functions that are provided by the - * framework for tasks commonly required in the processing of client - * messages. It is not strictly necessary to use these functions, but it - * both prudent and efficient to do so.

        - * - *

        These do not communicate with the Client in any way.

        - */ - - /* @{ */ - - /** @brief Load a number vector property value from the configuration file - * @param nvp pointer to a number vector property. - * @return Number of members successfully updated or 0 on failure - */ - extern int IULoadConfigNumber(const INumberVectorProperty *nvp); - - /** @brief Load a text vector property value from the configuration file - * @param tvp pointer to a text vector property. - * @return Number of members successfully updated or 0 on failure - */ - extern int IULoadConfigText(const ITextVectorProperty *tvp); - - /** @brief Add a switch vector property value to the configuration file - * @param svp pointer to a switch vector property. - * @return Number of members successfully updated or 0 on failure - */ - extern int IULoadConfigSwitch(const ISwitchVectorProperty *svp); - - /** @brief Function to reliably save new text in a IText. - * @param tp pointer to an IText member. - * @param newtext the new text to be saved - */ - extern void IUSaveText(IText *tp, const char *newtext); - - /** @brief Add a number vector property value to the configuration file - * @param fp file pointer to a configuration file. - * @param nvp pointer to a number vector property. - */ - extern void IUSaveConfigNumber(FILE *fp, const INumberVectorProperty *nvp); - - /** @brief Add a text vector property value to the configuration file - * @param fp file pointer to a configuration file. - * @param tvp pointer to a text vector property. - */ - extern void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp); - - /** @brief Add a switch vector property value to the configuration file - * @param fp file pointer to a configuration file. - * @param svp pointer to a switch vector property. - */ - extern void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp); - - /** @brief Add a BLOB vector property value to the configuration file - * @param fp file pointer to a configuration file. - * @param bvp pointer to a BLOB vector property. - * @note If the BLOB size is large, this function will block until the BLOB contents are written to the file. - */ - extern void IUSaveConfigBLOB(FILE *fp, const IBLOBVectorProperty *bvp); - - /** @brief Function to reliably save new text in a IText. - * @param tp pointer to an IText member. - * @param newtext the new text to be saved - */ - extern void IUSaveText(IText *tp, const char *newtext); - - /** @brief Function to save blob metadata in the corresponding blob. - * @param bp pointer to an IBLOB member. - * @param size size of the blob buffer encoded in base64 - * @param blobsize actual size of the buffer after base64 decoding. This is the actual byte count used in drivers. - * @param blob pointer to the blob buffer - * @param format format of the blob buffer - * @note Do not call this function directly, it is called internally by IUUpdateBLOB. - */ - extern int IUSaveBLOB(IBLOB *bp, int size, int blobsize, char *blob, char *format); - - /** @brief Find an IText member in a vector text property. - * @param tvp a pointer to a text vector property. - * @param name the name of the member to search for. - * @return a pointer to an IText member on match, or NULL if nothing is found. - */ - extern IText *IUFindText(const ITextVectorProperty *tvp, const char *name); - - /** @brief Find an INumber member in a number text property. - * @param nvp a pointer to a number vector property. - * @param name the name of the member to search for. - * @return a pointer to an INumber member on match, or NULL if nothing is found. - */ - extern INumber *IUFindNumber(const INumberVectorProperty *nvp, const char *name); - - /** @brief Find an ISwitch member in a vector switch property. - * @param svp a pointer to a switch vector property. - * @param name the name of the member to search for. - * @return a pointer to an ISwitch member on match, or NULL if nothing is found. - */ - extern ISwitch *IUFindSwitch(const ISwitchVectorProperty *svp, const char *name); - - /** @brief Find an ILight member in a vector Light property. - * @param lvp a pointer to a Light vector property. - * @param name the name of the member to search for. - * @return a pointer to an ILight member on match, or NULL if nothing is found. - */ - extern ILight *IUFindLight(const ILightVectorProperty *lvp, const char *name); - - /** @brief Find an IBLOB member in a vector BLOB property. - * @param bvp a pointer to a BLOB vector property. - * @param name the name of the member to search for. - * @return a pointer to an IBLOB member on match, or NULL if nothing is found. - */ - extern IBLOB *IUFindBLOB(const IBLOBVectorProperty *bvp, const char *name); - - /** @brief Returns the first ON switch it finds in the vector switch property. - * @note This is only valid for ISR_1OFMANY mode. That is, when only one switch out of many is allowed to be ON. Do not use this function if you can have multiple ON switches in the same vector property. - * @param sp a pointer to a switch vector property. - * @return a pointer to the \e first ON ISwitch member if found. If all switches are off, NULL is returned. - */ - extern ISwitch *IUFindOnSwitch(const ISwitchVectorProperty *sp); - - /** @brief Returns the index of the string in a string array - * @param needle the string to match against each element in the hay - * @param hay a pointer to a string array to search in - * @param n the size of hay - * @return index of needle if found in the hay. Otherwise -1 if not found. - */ - extern int IUFindIndex(const char *needle, char **hay, unsigned int n); - - /** @brief Returns the index of first ON switch it finds in the vector switch property. - * @note This is only valid for ISR_1OFMANY mode. That is, when only one switch out of many is allowed to be ON. Do not use this function if you can have multiple ON switches in the same vector property. - * @param sp a pointer to a switch vector property. - * @return index to the \e first ON ISwitch member if found. If all switches are off, -1 is returned. - */ - extern int IUFindOnSwitchIndex(const ISwitchVectorProperty *sp); - - /** @brief Returns the name of the first ON switch it finds in the supplied arguments. - * @note This is only valid for ISR_1OFMANY mode. That is, when only one switch out of many is allowed to be ON. Do not use this function if you can have multiple ON switches in the same vector property. - * @note This is a convience function intended to be used in ISNewSwitch(...) function to find out ON switch name without having to change actual switch state via IUUpdateSwitch(..) - * @param states list of switch states passed by ISNewSwitch() - * @param names list of switch names passed by ISNewSwitch() - * @param n number of switches passed by ISNewSwitch() - * @return name of the \e first ON ISwitch member if found. If all switches are off, NULL is returned. - */ - extern const char *IUFindOnSwitchName(ISState *states, char *names[], int n); - - /** @brief Reset all switches in a switch vector property to OFF. - * @param svp a pointer to a switch vector property. - */ - extern void IUResetSwitch(ISwitchVectorProperty *svp); - - /** @brief Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL. - * @param sp pointer a switch property to fill - * @param name the switch name - * @param label the switch label - * @param s the switch state (ISS_ON or ISS_OFF) - */ - extern void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s); - - /** @brief Assign attributes for a light property. The light's auxiliary elements will be set to NULL. - * @param lp pointer a light property to fill - * @param name the light name - * @param label the light label - * @param s the light state (IDLE, WARNING, OK, ALERT) - */ - extern void IUFillLight(ILight *lp, const char *name, const char *label, IPState s); - - /** @brief Assign attributes for a number property. The number's auxiliary elements will be set to NULL. - * @param np pointer a number property to fill - * @param name the number name - * @param label the number label - * @param format the number format in printf style (e.g. "%02d") - * @param min the minimum possible value - * @param max the maximum possible value - * @param step the step used to climb from minimum value to maximum value - * @param value the number's current value - */ - extern void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, - double step, double value); - - /** @brief Assign attributes for a text property. The text's auxiliary elements will be set to NULL. - * @param tp pointer a text property to fill - * @param name the text name - * @param label the text label - * @param initialText the initial text - */ - extern void IUFillText(IText *tp, const char *name, const char *label, const char *initialText); - - /** @brief Assign attributes for a BLOB property. The BLOB's data and auxiliary elements will be set to NULL. - * @param bp pointer a BLOB property to fill - * @param name the BLOB name - * @param label the BLOB label - * @param format the BLOB format. - */ - extern void IUFillBLOB(IBLOB *bp, const char *name, const char *label, const char *format); - - /** @brief Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL. - * @param svp pointer a switch vector property to fill - * @param sp pointer to an array of switches - * @param nsp the dimension of sp - * @param dev the device name this vector property belongs to - * @param name the vector property name - * @param label the vector property label - * @param group the vector property group - * @param p the vector property permission - * @param r the switches behavior - * @param timeout vector property timeout in seconds - * @param s the vector property initial state. - */ - extern void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, - const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s); - - /** @brief Assign attributes for a light vector property. The vector's auxiliary elements will be set to NULL. - * @param lvp pointer a light vector property to fill - * @param lp pointer to an array of lights - * @param nlp the dimension of lp - * @param dev the device name this vector property belongs to - * @param name the vector property name - * @param label the vector property label - * @param group the vector property group - * @param s the vector property initial state. - */ - extern void IUFillLightVector(ILightVectorProperty *lvp, ILight *lp, int nlp, const char *dev, const char *name, - const char *label, const char *group, IPState s); - - /** @brief Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL. - * @param nvp pointer a number vector property to fill - * @param np pointer to an array of numbers - * @param nnp the dimension of np - * @param dev the device name this vector property belongs to - * @param name the vector property name - * @param label the vector property label - * @param group the vector property group - * @param p the vector property permission - * @param timeout vector property timeout in seconds - * @param s the vector property initial state. - */ - extern void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, - const char *label, const char *group, IPerm p, double timeout, IPState s); - - /** @brief Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL. - * @param tvp pointer a text vector property to fill - * @param tp pointer to an array of texts - * @param ntp the dimension of tp - * @param dev the device name this vector property belongs to - * @param name the vector property name - * @param label the vector property label - * @param group the vector property group - * @param p the vector property permission - * @param timeout vector property timeout in seconds - * @param s the vector property initial state. - */ - extern void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, - const char *label, const char *group, IPerm p, double timeout, IPState s); - - /** @brief Assign attributes for a BLOB vector property. The vector's auxiliary elements will be set to NULL. - * @param bvp pointer a BLOB vector property to fill - * @param bp pointer to an array of BLOBs - * @param nbp the dimension of bp - * @param dev the device name this vector property belongs to - * @param name the vector property name - * @param label the vector property label - * @param group the vector property group - * @param p the vector property permission - * @param timeout vector property timeout in seconds - * @param s the vector property initial state. - */ - extern void IUFillBLOBVector(IBLOBVectorProperty *bvp, IBLOB *bp, int nbp, const char *dev, const char *name, - const char *label, const char *group, IPerm p, double timeout, IPState s); - - /** @brief Update a snooped number vector property from the given XML root element. - * @param root XML root elememnt containing the snopped property content - * @param nvp a pointer to the number vector property to be updated. - * @return 0 if cracking the XML element and updating the property proceeded without errors, -1 if trouble. - */ - extern int IUSnoopNumber(XMLEle *root, INumberVectorProperty *nvp); - - /** @brief Update a snooped text vector property from the given XML root element. - * @param root XML root elememnt containing the snopped property content - * @param tvp a pointer to the text vector property to be updated. - * @return 0 if cracking the XML element and updating the property proceeded without errors, -1 if trouble. - */ - extern int IUSnoopText(XMLEle *root, ITextVectorProperty *tvp); - - /** @brief Update a snooped light vector property from the given XML root element. - * @param root XML root elememnt containing the snopped property content - * @param lvp a pointer to the light vector property to be updated. - * @return 0 if cracking the XML element and updating the property proceeded without errors, -1 if trouble. - */ - extern int IUSnoopLight(XMLEle *root, ILightVectorProperty *lvp); - - /** @brief Update a snooped switch vector property from the given XML root element. - * @param root XML root elememnt containing the snopped property content - * @param svp a pointer to the switch vector property to be updated. - * @return 0 if cracking the XML element and updating the property proceeded without errors, -1 if trouble. - */ - extern int IUSnoopSwitch(XMLEle *root, ISwitchVectorProperty *svp); - - /** @brief Update a snooped BLOB vector property from the given XML root element. - * @param root XML root elememnt containing the snopped property content - * @param bvp a pointer to the BLOB vector property to be updated. - * @return 0 if cracking the XML element and updating the property proceeded without errors, -1 if trouble. - */ - extern int IUSnoopBLOB(XMLEle *root, IBLOBVectorProperty *bvp); - - /* @} */ - - /******************************************************************************* - ******************************************************************************* - * - * Functions the HYDROGEN Device Driver framework calls which the Driver must - * define. - * - ******************************************************************************* - ******************************************************************************* - */ - - /** - * @defgroup dcuFunctions IS Functions: Functions all drivers must define. - * - * This section defines functions that must be defined in each driver. These - * functions are never called by the driver, but are called by the driver - * framework. These must always be defined even if they do nothing. - */ - - /* @{ */ - - /** @brief Get Device Properties - * @param dev the name of the device. - * - * This function is called by the framework whenever the driver has received a - * getProperties message from an HYDROGEN client. The argument @param dev is either a - * string containing the name of the device specified within the message, or NULL - * if no device was specified. If the driver does not recognize the device, it - * should ignore the message and do nothing. If dev matches the device the driver - * is implementing, or dev is NULL, the driver must respond by sending one defXXX - * message to describe each property defined by this device, including its - * current (or initial) value. The recommended way to send these messages is to - * call the appropriate IDDef functions. - */ - extern void ISGetProperties(const char *dev); - - /** @brief Update the value of an existing text vector property. - * @param dev the name of the device. - * @param name the name of the text vector property to update. - * @param texts an array of text values. - * @param names parallel names to the array of text values. - * @param n the dimension of texts[]. - * @note You do not need to call this function, it is called by HYDROGEN when new text values arrive from the client. - */ - extern void ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n); - - /** @brief Update the value of an existing number vector property. - * @param dev the name of the device. - * @param name the name of the number vector property to update. - * @param values an array of number values. - * @param names parallel names to the array of number values. - * @param n the dimension of doubles[]. - * @note You do not need to call this function, it is called by HYDROGEN when new number values arrive from the client. - */ - extern void ISNewNumber(const char *dev, const char *name, double *values, char *names[], int n); - - /** @brief Update the value of an existing switch vector property. - * @param dev the name of the device. - * @param name the name of the switch vector property to update. - * @param states an array of switch states. - * @param names parallel names to the array of switch states. - * @param n the dimension of states[]. - * @note You do not need to call this function, it is called by HYDROGEN when new switch values arrive from the client. - */ - extern void ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n); - - /** @brief Update data of an existing blob vector property. - * @param dev the name of the device. - * @param name the name of the blob vector property to update. - * @param sizes an array of base64 blob sizes in bytes \e before decoding. - * @param blobsizes an array of the sizes of blobs \e after decoding from base64. - * @param blobs an array of decoded data. Each blob size is found in \e blobsizes array. - * @param formats Blob data format (e.g. fits.z). - * @param names names of blob members to update. - * @param n the number of blobs to update. - * @note You do not need to call this function, it is called by HYDROGEN when new blob values arrive from the client. - * e.g. BLOB element with name names[0] has data located in blobs[0] with size sizes[0] and format formats[0]. - */ - extern void ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], - char *names[], int n); - - /** @brief Function defined by Drivers that is called when another Driver it is snooping (by having previously called IDSnoopDevice()) sent any HYDROGEN message. - * @param root The argument contains the full message exactly as it was sent by the driver. - * \e Hint: use the IUSnoopXXX utility functions to help crack the message if it was one of setXXX or defXXX. - */ - extern void ISSnoopDevice(XMLEle *root); - - /* @} */ - - /** @brief Extract dev and name attributes from an XML element. - * @param root The XML element to be parsed. - * @param dev pointer to an allocated buffer to save the extracted element device name attribute. - * The buffer size must be at least MAXHYDROGENDEVICE bytes. - * @param name pointer to an allocated buffer to save the extracted elemented name attribute. - * The buffer size must be at least MAXHYDROGENNAME bytes. - * @param msg pointer to an allocated char buffer to store error messages. The minimum buffer size is MAXRBUF. - * @return 0 if successful, -1 if error is encountered and msg is set. - */ - extern int crackDN(XMLEle *root, char **dev, char **name, char msg[]); - - /** @brief Extract property state (Idle, OK, Busy, Alert) from the supplied string. - * @param str A string representation of the state. - * @param ip Pointer to IPState structure to store the extracted property state. - * @return 0 if successful, -1 if error is encountered. - */ - extern int crackIPState(const char *str, IPState *ip); - - /** @brief Extract switch state (On or Off) from the supplied string. - * @param str A string representation of the switch state. - * @param ip Pointer to ISState structure to store the extracted switch state. - * @return 0 if successful, -1 if error is encountered. - */ - extern int crackISState(const char *str, ISState *ip); - - /** @brief Extract property permission state (RW, RO, WO) from the supplied string. - * @param str A string representation of the permission state. - * @param ip Pointer to IPerm structure to store the extracted permission state. - * @return 0 if successful, -1 if error is encountered. - */ - extern int crackIPerm(const char *str, IPerm *ip); - - /** @brief Extract switch rule (OneOfMany, OnlyOne..etc) from the supplied string. - * @param str A string representation of the switch rule. - * @param ip Pointer to ISRule structure to store the extracted switch rule. - * @return 0 if successful, -1 if error is encountered. - */ - extern int crackISRule(const char *str, ISRule *ip); - - /** @return Returns a string representation of the supplied property state. */ - extern const char *pstateStr(IPState s); - - /** @return Returns a string representation of the supplied switch status. */ - extern const char *sstateStr(ISState s); - - /** @return Returns a string representation of the supplied switch rule. */ - extern const char *ruleStr(ISRule r); - - /** @return Returns a string representation of the supplied permission value. */ - extern const char *permStr(IPerm p); - - /** @brief print the boilerplate comment introducing xml */ - extern void xmlv1(); - -// Advertize support for shared blob on this platform -#define HYDROGEN_SHARED_BLOB_SUPPORT -#include "sharedblob.hpp" - -#ifdef __cplusplus -} -#endif diff --git a/src/core/base/hydrogendevapis.cpp b/src/core/base/hydrogendevapis.cpp deleted file mode 100644 index 0492215c..00000000 --- a/src/core/base/hydrogendevapis.cpp +++ /dev/null @@ -1,82 +0,0 @@ - -/** \section IUFindS */ - -/* find a member of an IText vector, else NULL */ -std::shared_ptr IUFindTextS(std::shared_ptr tvp, const char *name) -{ - for (int i = 0; i < tvp->ntp; i++) - if (strcmp(tvp->tp[i].name, name) == 0) - return (&tvp->tp[i]); - fprintf(stderr, "No IText '%s' in %s.%s\n", name, tvp->device, tvp->name); - return nullptr; -} - -/* find a member of an INumber vector, else NULL */ -std::shared_ptr IUFindNumberS(std::shared_ptr nvp, const char *name) -{ - for (int i = 0; i < nvp->nnp; i++) - if (strcmp(nvp->np[i].name, name) == 0) - return (&nvp->np[i]); - fprintf(stderr, "No INumber '%s' in %s.%s\n", name, nvp->device, nvp->name); - return nullptr; -} - -/* find a member of an ISwitch vector, else NULL */ -std::shared_ptr IUFindSwitchS(std::shared_ptr svp, const char *name) -{ - for (int i = 0; i < svp->nsp; i++) - if (strcmp(svp->sp[i].name, name) == 0) - return (&svp->sp[i]); - fprintf(stderr, "No ISwitch '%s' in %s.%s\n", name, svp->device, svp->name); - return nullptr; -} - -/* find a member of an ILight vector, else NULL */ -std::shared_ptr IUFindLightS(std::shared_ptr lvp, const char *name) -{ - for (int i = 0; i < lvp->nlp; i++) - if (strcmp(lvp->lp[i].name, name) == 0) - return (&lvp->lp[i]); - fprintf(stderr, "No ILight '%s' in %s.%s\n", name, lvp->device, lvp->name); - return nullptr; -} - -/* find a member of an IBLOB vector, else NULL */ -std::shared_ptr IUFindBLOBS(std::shared_ptr bvp, const char *name) -{ - for (int i = 0; i < bvp->nbp; i++) - if (strcmp(bvp->bp[i].name, name) == 0) - return (&bvp->bp[i]); - fprintf(stderr, "No IBLOB '%s' in %s.%s\n", name, bvp->device, bvp->name); - return nullptr; -} - -/* find an ON member of an ISwitch vector, else NULL. - * N.B. user must make sense of result with ISRule in mind. - */ -std::shared_ptr IUFindOnSwitchS(std::shared_ptr svp) -{ - for (int i = 0; i < svp->nsp; i++) - if (svp->sp[i].s == ISS_ON) - return (&svp->sp[i]); - /*fprintf(stderr, "No ISwitch On in %s.%s\n", svp->device, svp->name);*/ - return nullptr; -} - -/* Find index of the ON member of an ISwitchVectorProperty */ -int IUFindOnSwitchIndexS(std::shared_ptr svp) -{ - for (int i = 0; i < svp->nsp; i++) - if (svp->sp[i].s == ISS_ON) - return i; - return -1; -} - -/* Find name the ON member in the given states and names */ -const char *IUFindOnSwitchNameS(std::shared_ptr states, char *names[], int n) -{ - for (int i = 0; i < n; i++) - if (states[i] == ISS_ON) - return names[i]; - return nullptr; -} diff --git a/src/core/base/hydrogendevapis.hpp b/src/core/base/hydrogendevapis.hpp deleted file mode 100644 index 3c192369..00000000 --- a/src/core/base/hydrogendevapis.hpp +++ /dev/null @@ -1,59 +0,0 @@ - - /** @brief Find an IText member in a vector text property. - * @param tvp a pointer to a text vector property. - * @param name the name of the member to search for. - * @return a pointer to an IText member on match, or NULL if nothing is found. - */ - extern std::shared_ptr IUFindTextS(std::shared_ptr tvp, const char *name); - - /** @brief Find an INumber member in a number text property. - * @param nvp a pointer to a number vector property. - * @param name the name of the member to search for. - * @return a pointer to an INumber member on match, or NULL if nothing is found. - */ - extern std::shared_ptr IUFindNumberS(std::shared_ptr nvp, const char *name); - - /** @brief Find an ISwitch member in a vector switch property. - * @param svp a pointer to a switch vector property. - * @param name the name of the member to search for. - * @return a pointer to an ISwitch member on match, or NULL if nothing is found. - */ - extern std::shared_ptr IUFindSwitchS(std::shared_ptr svp, const char *name); - - /** @brief Find an ILight member in a vector Light property. - * @param lvp a pointer to a Light vector property. - * @param name the name of the member to search for. - * @return a pointer to an ILight member on match, or NULL if nothing is found. - */ - extern std::shared_ptr IUFindLightS(std::shared_ptr lvp, const char *name); - - /** @brief Find an IBLOB member in a vector BLOB property. - * @param bvp a pointer to a BLOB vector property. - * @param name the name of the member to search for. - * @return a pointer to an IBLOB member on match, or NULL if nothing is found. - */ - extern std::shared_ptr IUFindBLOBS(std::shared_ptr bvp, const char *name); - - /** @brief Returns the first ON switch it finds in the vector switch property. - * @note This is only valid for ISR_1OFMANY mode. That is, when only one switch out of many is allowed to be ON. Do not use this function if you can have multiple ON switches in the same vector property. - * @param sp a pointer to a switch vector property. - * @return a pointer to the \e first ON ISwitch member if found. If all switches are off, NULL is returned. - */ - extern std::shared_ptr IUFindOnSwitchS(std::shared_ptr svp); - - /** @brief Returns the index of first ON switch it finds in the vector switch property. - * @note This is only valid for ISR_1OFMANY mode. That is, when only one switch out of many is allowed to be ON. Do not use this function if you can have multiple ON switches in the same vector property. - * @param sp a pointer to a switch vector property. - * @return index to the \e first ON ISwitch member if found. If all switches are off, -1 is returned. - */ - extern int IUFindOnSwitchIndexS(std::shared_ptr svp); - - /** @brief Returns the name of the first ON switch it finds in the supplied arguments. - * @note This is only valid for ISR_1OFMANY mode. That is, when only one switch out of many is allowed to be ON. Do not use this function if you can have multiple ON switches in the same vector property. - * @note This is a convience function intended to be used in ISNewSwitch(...) function to find out ON switch name without having to change actual switch state via IUUpdateSwitch(..) - * @param states list of switch states passed by ISNewSwitch() - * @param names list of switch names passed by ISNewSwitch() - * @param n number of switches passed by ISNewSwitch() - * @return name of the \e first ON ISwitch member if found. If all switches are off, NULL is returned. - */ - extern const char *IUFindOnSwitchNameS(std::shared_ptr states, char *names[], int n); diff --git a/src/core/base/hydrogenlilxml.hpp b/src/core/base/hydrogenlilxml.hpp deleted file mode 100644 index fc027275..00000000 --- a/src/core/base/hydrogenlilxml.hpp +++ /dev/null @@ -1,522 +0,0 @@ -/* - Copyright (C) 2022 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#pragma once - -#include "lilxml.hpp" -#include "hydrogenapi.h" -#include "hydrogenbase.h" -#include "hydrogencom.hpp" -#include "hydrogendevapi.hpp" - -#include -#include -#include -#include -#include - -#include - -namespace HYDROGEN -{ - - // helper - template - class safe_ptr - { - T fake, *ptr; - - public: - safe_ptr(T *ptr = nullptr) : ptr(ptr ? ptr : &fake) - { - } - - T &operator*() { return *ptr; } - operator bool() const { return true; } - }; - - class LilXmlValue - { - public: - explicit LilXmlValue(const char *value); - explicit LilXmlValue(const char *value, size_t size); - - public: - bool isValid() const; - const void *data() const; - size_t size() const; - - public: - int toInt(safe_ptr ok = nullptr) const; - double toDoubleSexa(safe_ptr ok = nullptr) const; - double toDouble(safe_ptr ok = nullptr) const; - const char *toCString() const; - std::string toString() const; - ISRule toISRule(safe_ptr ok = nullptr) const; - ISState toISState(safe_ptr ok = nullptr) const; - IPState toIPState(safe_ptr ok = nullptr) const; - IPerm toIPerm(safe_ptr ok = nullptr) const; - - public: - std::size_t indexOf(const char *needle, size_t from = 0) const; - std::size_t indexOf(const std::string &needle, size_t from = 0) const; - - std::size_t lastIndexOf(const char *needle, size_t from = 0) const; - std::size_t lastIndexOf(const std::string &needle, size_t from = 0) const; - - bool startsWith(const char *needle) const; - bool startsWith(const std::string &needle) const; - - bool endsWith(const char *needle) const; - bool endsWith(const std::string &needle) const; - - public: - operator const char *() const { return toCString(); } - operator double() const { return toDouble(); } - operator int() const { return toInt(); } - operator ISRule() const { return toISRule(); } - operator ISState() const { return toISState(); } - operator IPState() const { return toIPState(); } - operator IPerm() const { return toIPerm(); } - operator size_t() const { return toInt(); } - - protected: - const char *mValue; - size_t mSize; - }; - - class LilXmlAttribute : public LilXmlValue - { - public: - explicit LilXmlAttribute(XMLAtt *a); - - public: - bool isValid() const; - - public: - std::string name() const; - const LilXmlValue &value() const { return *this; } - - public: - operator bool() const { return isValid(); } - - public: - XMLAtt *handle() const; - - protected: - XMLAtt *mHandle; - }; - - class LilXmlElement - { - public: - using Elements = std::list; // or implement custom container/iterators - - public: - explicit LilXmlElement(XMLEle *e); - - public: - bool isValid() const; - std::string tagName() const; - - public: - Elements getElements() const; - Elements getElementsByTagName(const char *tagName) const; - LilXmlAttribute getAttribute(const char *name) const; - LilXmlAttribute addAttribute(const char *name, const char *value); - void removeAttribute(const char *name); - - LilXmlValue context() const; - void setContext(const char *data); - - void print(FILE *f, int level = 0) const; - - public: - XMLEle *handle() const; - - protected: - XMLEle *mHandle; - char errmsg[128]; - }; - - class LilXmlDocument - { - LilXmlDocument(const LilXmlDocument &) = delete; - LilXmlDocument &operator=(const LilXmlDocument &) = delete; - - public: - explicit LilXmlDocument(XMLEle *root); - LilXmlDocument(LilXmlDocument &&other); - ~LilXmlDocument() = default; - - public: - bool isValid() const; - LilXmlElement root() const; - - protected: - std::unique_ptr mRoot; - }; - - class LilXmlParser - { - LilXmlParser(const LilXmlDocument &) = delete; - LilXmlParser &operator=(const LilXmlDocument &) = delete; - LilXmlParser(LilXmlDocument &&) = delete; - LilXmlParser &operator=(LilXmlDocument &&) = delete; - - public: - LilXmlParser(); - ~LilXmlParser() = default; - - public: - LilXmlDocument readFromFile(FILE *file); - LilXmlDocument readFromFile(const char *fileName); - LilXmlDocument readFromFile(const std::string &fileName); - - public: - std::list parseChunk(const char *data, size_t size); - - public: - bool hasErrorMessage() const; - const char *errorMessage() const; - - protected: - std::unique_ptr mHandle; - char mErrorMessage[MAXRBUF] = { - 0, - }; - }; - - // LilXmlValue Implementation - inline LilXmlValue::LilXmlValue(const char *value) - : mValue(value), mSize(value ? strlen(value) : 0) - { - } - - inline LilXmlValue::LilXmlValue(const char *value, size_t size) - : mValue(value), mSize(size) - { - } - - inline bool LilXmlValue::isValid() const - { - return mValue != nullptr; - } - - inline const void *LilXmlValue::data() const - { - return mValue; - } - - inline size_t LilXmlValue::size() const - { - return mSize; - } - - inline int LilXmlValue::toInt(safe_ptr ok) const - { - int result = 0; - try - { - result = std::stoi(toString()); - *ok = true; - } - catch (...) - { - *ok = false; - } - return result; - } - - inline double LilXmlValue::toDoubleSexa(safe_ptr ok) const - { - double result = 0; - *ok = (isValid() && f_scansexa(mValue, &result) >= 0); - return result; - } - - inline double LilXmlValue::toDouble(safe_ptr ok) const - { - double result = 0; - try - { - result = std::stod(toString()); - *ok = true; - } - catch (...) - { - *ok = false; - } - return result; - } - - inline const char *LilXmlValue::toCString() const - { - return isValid() ? mValue : ""; - } - - inline std::string LilXmlValue::toString() const - { - return isValid() ? mValue : ""; - } - - inline ISRule LilXmlValue::toISRule(safe_ptr ok) const - { - ISRule rule = ISR_1OFMANY; - *ok = (isValid() && crackISRule(mValue, &rule) >= 0); - return rule; - } - - inline ISState LilXmlValue::toISState(safe_ptr ok) const - { - ISState state = ISS_OFF; - *ok = (isValid() && crackISState(mValue, &state) >= 0); - return state; - } - - inline IPState LilXmlValue::toIPState(safe_ptr ok) const - { - IPState state = IPS_OK; - *ok = (isValid() && crackIPState(mValue, &state) >= 0); - return state; - } - - inline IPerm LilXmlValue::toIPerm(safe_ptr ok) const - { - IPerm result = IP_RO; - *ok = (isValid() && crackIPerm(mValue, &result) >= 0); - return result; - } - - inline std::size_t LilXmlValue::indexOf(const char *needle, size_t from) const - { - return toString().find_first_of(needle, from); - } - - inline std::size_t LilXmlValue::indexOf(const std::string &needle, size_t from) const - { - return toString().find_first_of(needle, from); - } - - inline std::size_t LilXmlValue::lastIndexOf(const char *needle, size_t from) const - { - return toString().find_last_of(needle, from); - } - - inline std::size_t LilXmlValue::lastIndexOf(const std::string &needle, size_t from) const - { - return toString().find_last_of(needle, from); - } - - inline bool LilXmlValue::startsWith(const char *needle) const - { - return indexOf(needle) == 0; - } - - inline bool LilXmlValue::startsWith(const std::string &needle) const - { - return indexOf(needle) == 0; - } - - inline bool LilXmlValue::endsWith(const char *needle) const - { - return lastIndexOf(needle) == (size() - strlen(needle)); - } - - inline bool LilXmlValue::endsWith(const std::string &needle) const - { - return lastIndexOf(needle) == (size() - needle.size()); - } - - // LilXmlAttribute Implementation - - inline LilXmlAttribute::LilXmlAttribute(XMLAtt *a) - : LilXmlValue(a ? valuXMLAtt(a) : nullptr), mHandle(a) - { - } - - inline XMLAtt *LilXmlAttribute::handle() const - { - return mHandle; - } - - inline bool LilXmlAttribute::isValid() const - { - return mHandle != nullptr; - } - - inline std::string LilXmlAttribute::name() const - { - return isValid() ? nameXMLAtt(mHandle) : ""; - } - - // LilXmlElement Implementation - - inline LilXmlElement::LilXmlElement(XMLEle *e) - : mHandle(e) - { - } - - inline XMLEle *LilXmlElement::handle() const - { - return mHandle; - } - - inline std::string LilXmlElement::tagName() const - { - return tagXMLEle(mHandle); - } - - inline LilXmlElement::Elements LilXmlElement::getElements() const - { - Elements result; - if (handle() == nullptr) - return result; - - for (XMLEle *ep = nextXMLEle(mHandle, 1); ep != nullptr; ep = nextXMLEle(mHandle, 0)) - result.push_back(LilXmlElement(ep)); - return result; - } - - inline LilXmlElement::Elements LilXmlElement::getElementsByTagName(const char *tagName) const - { - LilXmlElement::Elements result; - if (handle() == nullptr) - return result; - - for (XMLEle *ep = nextXMLEle(mHandle, 1); ep != nullptr; ep = nextXMLEle(mHandle, 0)) - { - LilXmlElement element(ep); - if (element.tagName() == tagName) - { - result.push_back(element); - } - } - return result; - } - - inline LilXmlAttribute LilXmlElement::getAttribute(const char *name) const - { - return LilXmlAttribute(findXMLAtt(mHandle, name)); - } - - inline LilXmlAttribute LilXmlElement::addAttribute(const char *name, const char *value) - { - return LilXmlAttribute(addXMLAtt(mHandle, name, value)); - } - - inline void LilXmlElement::removeAttribute(const char *name) - { - rmXMLAtt(mHandle, name); - } - - inline LilXmlValue LilXmlElement::context() const - { - return LilXmlValue(pcdataXMLEle(mHandle), pcdatalenXMLEle(mHandle)); - } - - inline void LilXmlElement::setContext(const char *data) - { - editXMLEle(mHandle, data); - } - - inline void LilXmlElement::print(FILE *f, int level) const - { - prXMLEle(f, handle(), level); - } - - // LilXmlDocument Implementation - - inline LilXmlDocument::LilXmlDocument(XMLEle *root) - : mRoot(root, [](XMLEle *root) - { if (root) delXMLEle(root); }) - { - } - - inline LilXmlDocument::LilXmlDocument(LilXmlDocument &&other) - : mRoot(std::move(other.mRoot)) - { - } - - inline bool LilXmlDocument::isValid() const - { - return mRoot != nullptr; - } - - inline LilXmlElement LilXmlDocument::root() const - { - return LilXmlElement(mRoot.get()); - } - - // LilXmlParser Implementation - - inline LilXmlParser::LilXmlParser() - : mHandle(newLilXML(), [](LilXML *handle) - { delLilXML(handle); }) - { - } - - inline LilXmlDocument LilXmlParser::readFromFile(FILE *file) - { - return LilXmlDocument(readXMLFile(file, mHandle.get(), mErrorMessage)); - } - - inline LilXmlDocument LilXmlParser::readFromFile(const char *fileName) - { - FILE *fp = fopen(fileName, "r"); - if (fp == nullptr) - { - snprintf(mErrorMessage, sizeof(mErrorMessage), "Error loading file %s", fileName); - return LilXmlDocument(nullptr); - } - - LilXmlDocument result = readFromFile(fp); - fclose(fp); - return result; - } - - inline LilXmlDocument LilXmlParser::readFromFile(const std::string &fileName) - { - return readFromFile(fileName.c_str()); - } - - inline std::list LilXmlParser::parseChunk(const char *data, size_t size) - { - std::list result; - XMLEle **nodes = parseXMLChunk(mHandle.get(), const_cast(data), int(size), mErrorMessage); - if (nodes != nullptr) - { - for (auto it = nodes; *it; ++it) - { - result.push_back(LilXmlDocument(*it)); - } - free(nodes); - } - return result; - } - - inline bool LilXmlParser::hasErrorMessage() const - { - return mErrorMessage[0] != '\0'; - } - - inline const char *LilXmlParser::errorMessage() const - { - return mErrorMessage; - } - -} diff --git a/src/core/base/hydrogenstandardproperty.cpp b/src/core/base/hydrogenstandardproperty.cpp deleted file mode 100644 index 5b481d20..00000000 --- a/src/core/base/hydrogenstandardproperty.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - Copyright(c) 2017 Jasem Mutlaq. All rights reserved. - - List of INDI Stanadrd Properties - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#include "hydrogenstandardproperty.h" - -namespace HYDROGEN -{ - namespace SP - { - const char *CONNECTION = "CONNECTION"; - const char *DEVICE_PORT = "DEVICE_PORT"; - const char *DEVICE_AUTO_SEARCH = "DEVICE_AUTO_SEARCH"; - const char *DEVICE_LAN_SEARCH = "DEVICE_LAN_SEARCH"; - const char *DEVICE_BAUD_RATE = "DEVICE_BAUD_RATE"; - const char *DEVICE_ADDRESS = "DEVICE_ADDRESS"; - } // namespace SP -} // namespace HYDROGEN diff --git a/src/core/base/hydrogenstandardproperty.h b/src/core/base/hydrogenstandardproperty.h deleted file mode 100644 index 314370d9..00000000 --- a/src/core/base/hydrogenstandardproperty.h +++ /dev/null @@ -1,159 +0,0 @@ -/******************************************************************************* - Copyright(c) 2017 Jasem Mutlaq. All rights reserved. - - List of INDI Stanadrd Properties - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#pragma once - -#include "hydrogenbase.h" - -namespace HYDROGEN -{ - - /** - * @namespace HYDROGEN::SP - @brief INDI Standard Properties are common properties standarized across drivers and clients alike. - - INDI does not place any special semantics on property names (i.e. properties are just texts, numbers, or switches that represent no physical function). While GUI clients can construct graphical representation of properties in order to permit the user to operate the device, we run into situations where clients and drivers need to agree on the exact meaning of some fundamental properties. - What if some client need to be aware of the existence of some property in order to perform some function useful to the user? How can that client tie itself to such a property if the property can be arbitrary defined by drivers? - - The solution is to define Standard Properties in order to establish a level of interoperability among INDI drivers and clients. We propose a set of shared INDI properties that encapsulate the most common characteristics of astronomical instrumentation of interest. - If the semantics of such properties are properly defined, not only it will insure base interoperability, but complete device automation becomes possible as well. Put another way, INDI standard properties are in essence properties that represent a clearly defined characteristic related to the operation of the device drivers. - - For example, a very common standard property is EQUATORIAL_EOD_COORD. This property represents the telescope's current RA and DEC. Clients need to be aware of this property in order to, for example, draw the telescope's cross hair on the sky map. If you write a script to control a telescope, you know that any telescope supporting EQUATORIAL_EOD_COORD will behave in an expected manner when the property is invoked. - INDI clients are required to honor standard properties if when and they implement any functions associated with a particular standard property. Furthermore, INDI drivers employing standard properties should strictly adhere to the standard properties structure as defined next. - - The properties are defined as string constants. To refer to the property in device drivers, use HYDROGEN::StandardProperty::PROPERTY_NAME or the shortcut HYDROGEN::SP::PROPERTY_NAME. - - The standard properties are divided into the following categories: -
          -
        1. @ref GeneralProperties "General Properties" shared across multiple devices.
        2. -
        3. @ref Connection::Interface "Connection Properties" -
            -
          • @ref SerialProperties "Serial Properties" used to communicate with and manage serial devices (including Bluetooth).
          • -
          • @ref TCPProperties "TCP Properties" used to communicate with and manage devices over the network.
          • -
          -
        4. -
        - @author Jasem Mutlaq - */ - namespace SP - { - /** - * \defgroup GeneralProperties Standard Properties - General: Common properties shared across devices of multiple genres. - * The following tables describe standard properties pertaining to generic devices. The name of a standard property and its members must be - * strictly reserved in all drivers. However, it is permissible to change the label element of properties. You can find numerous uses of the - * standard properties in the INDI library driver repository. - * - * As a general rule of the thumb, the status of properties reflects the command execution result: - * IPS_OKAY: Command excuted successfully. - * IPS_BUSY: Command execution under progress. - * IPS_ALERT: Command execution failed. - */ - - /*@{*/ - - /** - * @brief Connect to and disconnect from device. - * Name | Type | Member | Default | Description - * ---- | ---- | ------ | ------- | ----------- - * CONNECTION | SWITCH | CONNECT | OFF | Establish connection to device - * CONNECTION | SWITCH | DISCONNECT | ON | Disconnect device - */ - extern const char *CONNECTION; - - /*@}*/ - - /** - * \defgroup SerialProperties Standard Properties - Serial: Properties used to communicate with and manage serial devices. - * Serial communication over RS232/485 and Bluetooth. Unless otherwise noted, all the properties are saved in the configuration file so that they are remembered across sessions. - */ - - /*@{*/ - - /** - * @brief Device serial (or bluetooth) connection port. The default value on Linux is /dev/ttyUSB0 while on MacOS it is /dev/cu.usbserial - * It is part of Connection::SerialInterface to manage connections to serial devices. - * Name | Type | Member | Default | Description - * ---- | ---- | ------ | ------- | ----------- - * DEVICE_PORT | TEXT | PORT | /dev/ttyUSB0 | Device serial connection port - */ - extern const char *DEVICE_PORT; - - /** - * @brief Toggle device auto search. - * If enabled and on connection failure with the default port, the SerialInterface class shall scan the system for available - * serial ports and attempts connection and handshake with each until successful. Please note if this option is enabled it can take - * a while before connection is established depending on how many ports are available on the system and the handshake timeout of the - * the underlying device. - * Name | Type | Member | Default | Description - * ---- | ---- | ------ | ------- | ----------- - * DEVICE_AUTO_SEARCH | SWITCH | HYDROGEN_ENABLED | ON | Auto Search ON - * DEVICE_AUTO_SEARCH | SWITCH | HYDROGEN_DISABLED | OFF | Auto Search OFF - */ - extern const char *DEVICE_AUTO_SEARCH; - - /** - * @brief Toggle device LAN search. - * If the initial handshake with the specified hostname and port number fails, then scan the local - * network using all available interfaces for any hosts that accepts connection on the specified port. - * If connection is established, attempt a handshake. On handshake failure, iterate to the next host detected - * in the local network and try again. - * Name | Type | Member | Default | Description - * ---- | ---- | ------ | ------- | ----------- - * DEVICE_LAN_SEARCH | SWITCH | HYDROGEN_ENABLED | OFF | LAN Search ON - * DEVICE_LAN_SEARCH | SWITCH | HYDROGEN_DISABLED | ON | LAN Search OFF - */ - extern const char *DEVICE_LAN_SEARCH; - - /** - * @brief Set device baud rate - * Name | Type | Member | Default | Description - * ---- | ---- | ------ | ------- | ----------- - * DEVICE_BAUD_RATE | SWITCH | 9600 | ON | 9600 - * DEVICE_BAUD_RATE | SWITCH | 19200 | OFF | 19200 - * DEVICE_BAUD_RATE | SWITCH | 38400 | OFF | 38400 - * DEVICE_BAUD_RATE | SWITCH | 57600 | OFF | 57600 - * DEVICE_BAUD_RATE | SWITCH | 115200 | OFF | 115200 - * DEVICE_BAUD_RATE | SWITCH | 230400 | OFF | 230400 - */ - extern const char *DEVICE_BAUD_RATE; - - /*@}*/ - - /** - * \defgroup TCPProperties Standard Properties - TCP: Properties used to communicate with and manage devices over the network. - * Communication with devices over TCP/IP. Unless otherwise noted, all the properties are saved in the configuration file so that they are remembered across sessions. - */ - - /*@{*/ - - /** - * @brief Device hostname and port. - * It is part of Connection::TCPInterface to manage connections to devices over the network. - * Name | Type | Member | Default | Description - * ---- | ---- | ------ | ------- | ----------- - * DEVICE_ADDRESS | TEXT | ADDRESS | | Device hostname or IP Address - * DEVICE_ADDRESS | TEXT | PORT | | Device port - */ - extern const char *DEVICE_ADDRESS; - - /*@}*/ - - } -} // namespace HYDROGEN diff --git a/src/core/base/hydrogenuserio.cpp b/src/core/base/hydrogenuserio.cpp deleted file mode 100644 index 6d648620..00000000 --- a/src/core/base/hydrogenuserio.cpp +++ /dev/null @@ -1,786 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "hydrogenuserio.hpp" -#include "hydrogenapi.h" -#include "hydrogendevapi.hpp" -#include "hydrogencom.hpp" -#include "locale/locale_compat.hpp" -#include "base64.hpp" - -#include -#include - -static void s_userio_xml_message_vprintf(const userio *io, void *user, const char *fmt, va_list ap) -{ - char message[MAXHYDROGENMESSAGE]; - if (fmt) - { - vsnprintf(message, MAXHYDROGENMESSAGE, fmt, ap); - - userio_prints(io, user, " message='"); - userio_xml_escape(io, user, message); - userio_prints(io, user, "'\n"); - } -} - -void IUUserIONumberContext(const userio *io, void *user, const INumberVectorProperty *nvp) -{ - for (int i = 0; i < nvp->nnp; i++) - { - INumber *np = &nvp->np[i]; - userio_prints(io, user, " \n"); - userio_printf(io, user, " %.20g\n", np->value); // safe - userio_prints(io, user, " \n"); - } -} - -void IUUserIOTextContext(const userio *io, void *user, const ITextVectorProperty *tvp) -{ - for (int i = 0; i < tvp->ntp; i++) - { - IText *tp = &tvp->tp[i]; - userio_prints(io, user, " \n" - " "); - if (tp->text) - userio_xml_escape(io, user, tp->text); - userio_prints(io, user, "\n" - " \n"); - } -} - -void IUUserIOSwitchContextOne(const userio *io, void *user, const ISwitch *sp) -{ - userio_prints(io, user, " \n" - " "); - userio_prints(io, user, sstateStr(sp->s)); - userio_prints(io, user, "\n" - " \n"); -} - -void IUUserIOSwitchContextFull(const userio *io, void *user, const ISwitchVectorProperty *svp) -{ - for (int i = 0; i < svp->nsp; i++) - { - ISwitch *sp = &svp->sp[i]; - IUUserIOSwitchContextOne(io, user, sp); - } -} - -void IUUserIOSwitchContext(const userio *io, void *user, const ISwitchVectorProperty *svp) -{ - ISwitch *onSwitch = IUFindOnSwitch(svp); - - if (svp->r == ISR_1OFMANY && onSwitch) - IUUserIOSwitchContextOne(io, user, onSwitch); - else - IUUserIOSwitchContextFull(io, user, svp); -} - -void IUUserIOBLOBContextOne( - const userio *io, void *user, - const char *name, unsigned int size, unsigned int bloblen, const void *blob, const char *format) -{ - unsigned char *encblob; - int l; - - userio_prints(io, user, " \n"); - } - else - { - if (io->joinbuff) - { - userio_prints(io, user, " format='"); - userio_xml_escape(io, user, format); - userio_prints(io, user, "'\n"); - userio_printf(io, user, " len='%d'\n", bloblen); - - io->joinbuff(user, " attached='true'>\n", (void *)blob, bloblen); - } - else - { - size_t sz = 4 * bloblen / 3 + 4; - assert_mem(encblob = (unsigned char *)malloc(sz)); // #PS: TODO - l = to64frombits_s(encblob, reinterpret_cast(blob), bloblen, sz); - if (l == 0) - { - fprintf(stderr, "%s: Not enough memory for decoding.\n", __func__); - exit(1); - } - userio_printf(io, user, " enclen='%d'\n", l); // safe - userio_prints(io, user, " format='"); - userio_xml_escape(io, user, format); - userio_prints(io, user, "'>\n"); - size_t written = 0; - // FIXME: this is not efficient. The CR/LF imply one more copy... Do we need them ? - while ((int)written < l) - { - size_t towrite = ((l - written) > 72) ? 72 : l - written; - size_t wr = userio_write(io, user, encblob + written, towrite); - - if (wr == 0) - { - free(encblob); - return; - } - - written += wr; - if ((written % 72) == 0) - userio_putc(io, user, '\n'); - } - - if ((written % 72) != 0) - userio_putc(io, user, '\n'); - - free(encblob); - } - } - - userio_prints(io, user, " \n"); -} - -void IUUserIOBLOBContext(const userio *io, void *user, const IBLOBVectorProperty *bvp) -{ - for (int i = 0; i < bvp->nbp; i++) - { - IBLOB *bp = &bvp->bp[i]; - IUUserIOBLOBContextOne( - io, user, - bp->name, bp->size, bp->bloblen, bp->blob, bp->format); - } -} - -void IUUserIOLightContext(const userio *io, void *user, const ILightVectorProperty *lvp) -{ - for (int i = 0; i < lvp->nlp; i++) - { - ILight *lp = &lvp->lp[i]; - userio_prints(io, user, " \n" - " "); - userio_prints(io, user, pstateStr(lp->s)); - userio_prints(io, user, "\n" - " \n"); - } -} - -void IUUserIONewNumber(const userio *io, void *user, const INumberVectorProperty *nvp) -{ - locale_char_t *orig = hydrogen_locale_C_numeric_push(); - - userio_prints(io, user, "\n"); - - IUUserIONumberContext(io, user, nvp); - - userio_prints(io, user, "\n"); - - hydrogen_locale_C_numeric_pop(orig); -} - -void IUUserIONewText(const userio *io, void *user, const ITextVectorProperty *tvp) -{ - userio_prints(io, user, "\n"); - - IUUserIOTextContext(io, user, tvp); - - userio_prints(io, user, "\n"); -} - -void IUUserIONewSwitchFull(const userio *io, void *user, const ISwitchVectorProperty *svp) -{ - userio_prints(io, user, "\n"); - IUUserIOSwitchContextFull(io, user, svp); - userio_prints(io, user, "\n"); -} - -void IUUserIONewSwitch(const userio *io, void *user, const ISwitchVectorProperty *svp) -{ - userio_prints(io, user, "\n"); - IUUserIOSwitchContext(io, user, svp); - userio_prints(io, user, "\n"); -} - -void IUUserIONewBLOB(const userio *io, void *user, const IBLOBVectorProperty *bvp) -{ - IUUserIONewBLOBStart(io, user, bvp->device, bvp->name, NULL); - - IUUserIOBLOBContext(io, user, bvp); - - IUUserIONewBLOBFinish(io, user); -} - -void IUUserIONewBLOBStart( - const userio *io, void *user, - const char *dev, const char *name, const char *timestamp) -{ - userio_prints(io, user, "\n"); -} - -void IUUserIONewBLOBFinish(const userio *io, void *user) -{ - userio_prints(io, user, "\n"); -} - -void IUUserIODeleteVA( - const userio *io, void *user, - const char *dev, const char *name, const char *fmt, va_list ap) -{ - userio_prints(io, user, "\n"); -} - -void IUUserIOGetProperties( - const userio *io, void *user, - const char *dev, const char *name) -{ - userio_printf(io, user, "\n"); -} - -// temporary -static const char *s_BLOBHandlingtoString(BLOBHandling bh) -{ - switch (bh) - { - case B_NEVER: - return "Never"; - case B_ALSO: - return "Also"; - case B_ONLY: - return "Only"; - default: - return "Unknown"; - } -} - -void IUUserIOEnableBLOB( - const userio *io, void *user, - const char *dev, const char *name, BLOBHandling blobH) -{ - userio_prints(io, user, ""); - userio_prints(io, user, s_BLOBHandlingtoString(blobH)); - userio_prints(io, user, "\n"); -} - -void IDUserIOMessageVA( - const userio *io, void *user, - const char *dev, const char *fmt, va_list ap) -{ - userio_prints(io, user, "\n"); -} - -void IDUserIOMessage( - const userio *io, void *user, - const char *dev, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - IDUserIOMessageVA(io, user, dev, fmt, ap); - va_end(ap); -} - -void IUUserIOConfigTag( - const userio *io, void *user, int ctag) -{ - /* Opening tag */ - if (ctag == 0) - { - userio_prints(io, user, "\n"); - } - /* Closing tag */ - else - { - userio_prints(io, user, "\n"); - } -} - -void IUUserIODefTextVA( - const userio *io, void *user, - const ITextVectorProperty *tvp, const char *fmt, va_list ap) -{ - locale_char_t *orig = hydrogen_locale_C_numeric_push(); - userio_prints(io, user, "s)); // safe - userio_printf(io, user, " perm='%s'\n", permStr(tvp->p)); // safe - userio_printf(io, user, " timeout='%g'\n", tvp->timeout); // safe - userio_printf(io, user, " timestamp='%s'\n", indi_timestamp()); // safe - s_userio_xml_message_vprintf(io, user, fmt, ap); - userio_prints(io, user, ">\n"); - - for (int i = 0; i < tvp->ntp; i++) - { - IText *tp = &tvp->tp[i]; - userio_prints(io, user, " \n" - " "); - if (tp->text) - userio_xml_escape(io, user, tp->text); - userio_prints(io, user, "\n" - " \n"); - } - - userio_prints(io, user, "\n"); - hydrogen_locale_C_numeric_pop(orig); -} - -void IUUserIODefNumberVA( - const userio *io, void *user, - const INumberVectorProperty *n, const char *fmt, va_list ap) -{ - locale_char_t *orig = hydrogen_locale_C_numeric_push(); - userio_prints(io, user, "s)); // safe - userio_printf(io, user, " perm='%s'\n", permStr(n->p)); // safe - userio_printf(io, user, " timeout='%g'\n", n->timeout); // safe - userio_printf(io, user, " timestamp='%s'\n", indi_timestamp()); // safe - s_userio_xml_message_vprintf(io, user, fmt, ap); - userio_prints(io, user, ">\n"); - - for (int i = 0; i < n->nnp; i++) - { - INumber *np = &n->np[i]; - - userio_prints(io, user, " min); // safe - userio_printf(io, user, " max='%.20g'\n", np->max); // safe - userio_printf(io, user, " step='%.20g'>\n", np->step); // safe - userio_printf(io, user, " %.20g\n", np->value); // safe - - userio_prints(io, user, " \n"); - } - - userio_prints(io, user, "\n"); - hydrogen_locale_C_numeric_pop(orig); -} - -void IUUserIODefSwitchVA( - const userio *io, void *user, - const ISwitchVectorProperty *s, const char *fmt, va_list ap) -{ - locale_char_t *orig = hydrogen_locale_C_numeric_push(); - userio_prints(io, user, "s)); // safe - userio_printf(io, user, " perm='%s'\n", permStr(s->p)); // safe - userio_printf(io, user, " rule='%s'\n", ruleStr(s->r)); // safe - userio_printf(io, user, " timeout='%g'\n", s->timeout); // safe - userio_printf(io, user, " timestamp='%s'\n", indi_timestamp()); // safe - s_userio_xml_message_vprintf(io, user, fmt, ap); - userio_prints(io, user, ">\n"); - - for (int i = 0; i < s->nsp; i++) - { - ISwitch *sp = &s->sp[i]; - userio_prints(io, user, " \n"); - userio_printf(io, user, " %s\n", sstateStr(sp->s)); // safe - userio_prints(io, user, " \n"); - } - - userio_prints(io, user, "\n"); - hydrogen_locale_C_numeric_pop(orig); -} - -void IUUserIODefLightVA( - const userio *io, void *user, - const ILightVectorProperty *lvp, const char *fmt, va_list ap) -{ - userio_prints(io, user, "s)); // safe - userio_printf(io, user, " timestamp='%s'\n", indi_timestamp()); // safe - s_userio_xml_message_vprintf(io, user, fmt, ap); - userio_prints(io, user, ">\n"); - - for (int i = 0; i < lvp->nlp; i++) - { - ILight *lp = &lvp->lp[i]; - userio_prints(io, user, " \n"); - userio_printf(io, user, " %s\n", pstateStr(lp->s)); // safe - userio_prints(io, user, " \n"); - } - - userio_prints(io, user, "\n"); -} - -void IUUserIODefBLOBVA( - const userio *io, void *user, - const IBLOBVectorProperty *b, const char *fmt, va_list ap) -{ - locale_char_t *orig = hydrogen_locale_C_numeric_push(); - userio_prints(io, user, "s)); // safe - userio_printf(io, user, " perm='%s'\n", permStr(b->p)); // safe - userio_printf(io, user, " timeout='%g'\n", b->timeout); // safe - userio_printf(io, user, " timestamp='%s'\n", indi_timestamp()); // safe - s_userio_xml_message_vprintf(io, user, fmt, ap); - userio_prints(io, user, ">\n"); - - for (int i = 0; i < b->nbp; i++) - { - IBLOB *bp = &b->bp[i]; - userio_prints(io, user, " \n"); - } - - userio_prints(io, user, "\n"); - hydrogen_locale_C_numeric_pop(orig); -} - -void IUUserIOSetTextVA( - const userio *io, void *user, - const ITextVectorProperty *tvp, const char *fmt, va_list ap) -{ - locale_char_t *orig = hydrogen_locale_C_numeric_push(); - userio_prints(io, user, "s)); // safe - userio_printf(io, user, " timeout='%g'\n", tvp->timeout); // safe - userio_printf(io, user, " timestamp='%s'\n", indi_timestamp()); // safe - s_userio_xml_message_vprintf(io, user, fmt, ap); - userio_prints(io, user, ">\n"); - - IUUserIOTextContext(io, user, tvp); - - userio_prints(io, user, "\n"); - hydrogen_locale_C_numeric_pop(orig); -} - -void IUUserIOSetNumberVA( - const userio *io, void *user, - const INumberVectorProperty *nvp, const char *fmt, va_list ap) -{ - locale_char_t *orig = hydrogen_locale_C_numeric_push(); - userio_prints(io, user, "s)); // safe - userio_printf(io, user, " timeout='%g'\n", nvp->timeout); // safe - userio_printf(io, user, " timestamp='%s'\n", indi_timestamp()); // safe - s_userio_xml_message_vprintf(io, user, fmt, ap); - userio_prints(io, user, ">\n"); - - IUUserIONumberContext(io, user, nvp); - - userio_prints(io, user, "\n"); - hydrogen_locale_C_numeric_pop(orig); -} - -void IUUserIOSetSwitchVA( - const userio *io, void *user, - const ISwitchVectorProperty *svp, const char *fmt, va_list ap) -{ - locale_char_t *orig = hydrogen_locale_C_numeric_push(); - userio_prints(io, user, "s)); // safe - userio_printf(io, user, " timeout='%g'\n", svp->timeout); // safe - userio_printf(io, user, " timestamp='%s'\n", indi_timestamp()); // safe - s_userio_xml_message_vprintf(io, user, fmt, ap); - userio_prints(io, user, ">\n"); - - IUUserIOSwitchContextFull(io, user, svp); - - userio_prints(io, user, "\n"); - hydrogen_locale_C_numeric_pop(orig); -} - -void IUUserIOSetLightVA( - const userio *io, void *user, - const ILightVectorProperty *lvp, const char *fmt, va_list ap) -{ - userio_prints(io, user, "s)); // safe - userio_printf(io, user, " timestamp='%s'\n", indi_timestamp()); // safe - s_userio_xml_message_vprintf(io, user, fmt, ap); - userio_prints(io, user, ">\n"); - - IUUserIOLightContext(io, user, lvp); - - userio_prints(io, user, "\n"); -} - -void IUUserIOSetBLOBVA( - const userio *io, void *user, - const IBLOBVectorProperty *bvp, const char *fmt, va_list ap) -{ - locale_char_t *orig = hydrogen_locale_C_numeric_push(); - userio_prints(io, user, "s)); // safe - userio_printf(io, user, " timeout='%g'\n", bvp->timeout); // safe - userio_printf(io, user, " timestamp='%s'\n", indi_timestamp()); // safe - s_userio_xml_message_vprintf(io, user, fmt, ap); - userio_prints(io, user, ">\n"); - - IUUserIOBLOBContext(io, user, bvp); - - userio_prints(io, user, "\n"); - hydrogen_locale_C_numeric_pop(orig); -} - -void IUUserIOUpdateMinMax( - const userio *io, void *user, - const INumberVectorProperty *nvp) -{ - locale_char_t *orig = hydrogen_locale_C_numeric_push(); - userio_prints(io, user, "s)); // safe - userio_printf(io, user, " timeout='%g'\n", nvp->timeout); // safe - userio_printf(io, user, " timestamp='%s'\n", indi_timestamp()); // safe - userio_prints(io, user, ">\n"); - - for (int i = 0; i < nvp->nnp; i++) - { - INumber *np = &nvp->np[i]; - userio_prints(io, user, " min); // safe - userio_printf(io, user, " max='%g'\n", np->max); // safe - userio_printf(io, user, " step='%g'\n", np->step); // safe - userio_prints(io, user, ">\n"); - userio_printf(io, user, " %g\n", np->value); // safe - userio_prints(io, user, " \n"); - } - - userio_prints(io, user, "\n"); - hydrogen_locale_C_numeric_pop(orig); -} - -void IUUserIOPingRequest(const userio *io, void *user, const char *pingUid) -{ - userio_prints(io, user, "\n"); -} - -void IUUserIOPingReply(const userio *io, void *user, const char *pingUid) -{ - userio_prints(io, user, "\n"); -} diff --git a/src/core/base/hydrogenuserio.hpp b/src/core/base/hydrogenuserio.hpp deleted file mode 100644 index 0fbb3aec..00000000 --- a/src/core/base/hydrogenuserio.hpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - 2022 by Ludovic Pollet - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#pragma once - -#include "userio.hpp" -#include "hydrogendevapi.hpp" - -struct _ITextVectorProperty; -struct _INumberVectorProperty; -struct _ISwitchVectorProperty; -struct _IBLOBVectorProperty; -struct _ILightVectorProperty; - -struct _IBLOB; -struct _ISwitch; - -void IUUserIOTextContext(const userio *io, void *user, const struct _ITextVectorProperty *tvp); -void IUUserIONumberContext(const userio *io, void *user, const struct _INumberVectorProperty *nvp); -void IUUserIOSwitchContextOne(const userio *io, void *user, const struct _ISwitch *sp); -void IUUserIOSwitchContextFull(const userio *io, void *user, const ISwitchVectorProperty *svp); -void IUUserIOSwitchContext(const userio *io, void *user, const struct _ISwitchVectorProperty *svp); -void IUUserIOBLOBContext(const userio *io, void *user, const struct _IBLOBVectorProperty *bvp); -void IUUserIOLightContext(const userio *io, void *user, const struct _ILightVectorProperty *lvp); - -void IUUserIONewText(const userio *io, void *user, const struct _ITextVectorProperty *tvp); -void IUUserIONewNumber(const userio *io, void *user, const struct _INumberVectorProperty *nvp); -void IUUserIONewSwitchFull(const userio *io, void *user, const ISwitchVectorProperty *svp); -void IUUserIONewSwitch(const userio *io, void *user, const struct _ISwitchVectorProperty *svp); -void IUUserIONewBLOB(const userio *io, void *user, const struct _IBLOBVectorProperty *bvp); - -void IUUserIONewBLOBStart(const userio *io, void *user, const char *dev, const char *name, const char *timestamp); - -void IUUserIOBLOBContextOne( - const userio *io, void *user, - const char *name, unsigned int size, unsigned int bloblen, const void *blob, const char *format); - -void IUUserIONewBLOBFinish(const userio *io, void *user); - -void IUUserIOEnableBLOB( - const userio *io, void *user, - const char *dev, const char *name, BLOBHandling blobH); - -// Define -void IUUserIODefTextVA(const userio *io, void *user, const struct _ITextVectorProperty *tvp, const char *fmt, va_list ap); -void IUUserIODefNumberVA(const userio *io, void *user, const struct _INumberVectorProperty *n, const char *fmt, va_list ap); -void IUUserIODefSwitchVA(const userio *io, void *user, const struct _ISwitchVectorProperty *s, const char *fmt, va_list ap); -void IUUserIODefLightVA(const userio *io, void *user, const struct _ILightVectorProperty *lvp, const char *fmt, va_list ap); -void IUUserIODefBLOBVA(const userio *io, void *user, const struct _IBLOBVectorProperty *b, const char *fmt, va_list ap); - -// Setup -void IUUserIOSetTextVA(const userio *io, void *user, const struct _ITextVectorProperty *tvp, const char *fmt, va_list ap); -void IUUserIOSetNumberVA(const userio *io, void *user, const struct _INumberVectorProperty *nvp, const char *fmt, - va_list ap); -void IUUserIOSetSwitchVA(const userio *io, void *user, const struct _ISwitchVectorProperty *svp, const char *fmt, - va_list ap); -void IUUserIOSetLightVA(const userio *io, void *user, const struct _ILightVectorProperty *lvp, const char *fmt, va_list ap); -void IUUserIOSetBLOBVA(const userio *io, void *user, const struct _IBLOBVectorProperty *bvp, const char *fmt, va_list ap); - -void IUUserIOUpdateMinMax(const userio *io, void *user, const struct _INumberVectorProperty *nvp); - -void IUUserIODeleteVA(const userio *io, void *user, const char *dev, const char *name, const char *fmt, va_list ap); - -void IUUserIOGetProperties(const userio *io, void *user, const char *dev, const char *name); - -void IDUserIOMessage(const userio *io, void *user, const char *dev, const char *fmt, ...); -void IDUserIOMessageVA(const userio *io, void *user, const char *dev, const char *fmt, va_list ap); - -void IUUserIOConfigTag(const userio *io, void *user, int ctag); - -void IUUserIOPingRequest(const userio *io, void *user, const char *pingUid); -void IUUserIOPingReply(const userio *io, void *user, const char *pingUid); diff --git a/src/core/base/hydrogenutility.cpp b/src/core/base/hydrogenutility.cpp deleted file mode 100644 index 6085e567..00000000 --- a/src/core/base/hydrogenutility.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - Copyright (C) 2020 by Pawel Soja - Copyright (C) 2015 by Jasem Mutlaq - Copyright (C) 2014 by geehalel - - Stream Recorder - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -*/ -#include "hydrogenutility.hpp" -#include - -#ifdef _MSC_VER - -#include - -#ifndef S_ISDIR -#define S_ISDIR(m) (((m)&_S_IFDIR) == _S_IFDIR) -#endif - -#define mkdir _mkdir - -#endif - -namespace HYDROGEN -{ - - int mkdir(const char *path, mode_t mode) - { -#ifdef _WIN32 - HYDROGEN_UNUSED(mode); - return ::mkdir(path); -#else - return ::mkdir(path, mode); -#endif - } - - int mkpath(std::string s, mode_t mode) - { - size_t pre = 0, pos; - std::string dir; - int mdret = 0; - struct stat st; - - if (s[s.size() - 1] != '/') - s += '/'; - - while ((pos = s.find_first_of('/', pre)) != std::string::npos) - { - dir = s.substr(0, pos++); - pre = pos; - if (dir.size() == 0) - continue; - - if (stat(dir.c_str(), &st)) - { - if (errno != ENOENT || ((mdret = mkdir(dir.c_str(), mode)) && errno != EEXIST)) - { - return mdret; - } - } - else - { - if (!S_ISDIR(st.st_mode)) - { - return -1; - } - } - } - return mdret; - } - - std::string format_time(const std::tm &tm, const char *format) - { - char cstr[32]; - - std::strftime(cstr, sizeof(cstr), format, &tm); - - return std::string(cstr); - } - - void replace_all(std::string &subject, const std::string &search, const std::string &replace) - { - size_t pos = 0; - while ((pos = subject.find(search, pos)) != std::string::npos) - { - subject.replace(pos, search.length(), replace); - pos += replace.length(); - } - } - -} diff --git a/src/core/base/hydrogenutility.hpp b/src/core/base/hydrogenutility.hpp deleted file mode 100644 index 8a8061e2..00000000 --- a/src/core/base/hydrogenutility.hpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - Copyright (C) 2020 by Pawel Soja - Copyright (C) 2015 by Jasem Mutlaq - Copyright (C) 2014 by geehalel - - Stream Recorder - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -*/ -#pragma once - -#ifdef __cplusplus -#include -#include -#include -#include - -#include "util/macro.hpp" -#else -#include -#endif - -// C -#ifdef __cplusplus -extern "C" -{ -#endif - /** - * @brief The strlcpy() function copy strings respectively. - * They are designed to be safer, more consistent, and less error prone replacements for strncpy. - */ - inline static size_t indi_strlcpy(char *dst, const char *src, size_t maxlen) - { - const size_t srclen = strlen(src); - if (srclen + 1 < maxlen) - { - memcpy(dst, src, srclen + 1); - } - else if (maxlen != 0) - { - memcpy(dst, src, maxlen - 1); - dst[maxlen - 1] = '\0'; - } - return srclen; - } -#ifdef __cplusplus -} -#endif - -// C++ -#ifdef __cplusplus - -#ifdef _WINDOWS -typedef int mode_t; -#endif - -namespace HYDROGEN -{ - /** - * @brief Create directory. - */ - int mkdir(const char *path, mode_t mode); - - /** - * @brief Create a path directory - this function uses 'mkdir' - */ - int mkpath(std::string path, mode_t mode); - - /** - * @brief Converts the date and time to string - this function uses 'strftime' - */ - std::string format_time(const std::tm &tm, const char *format); - - /** - * @brief Replaces every occurrence of the string 'search' with the string 'replace' - */ - void replace_all(std::string &subject, const std::string &search, const std::string &replace); - - /** - * @brief The strlcpy() function copy strings respectively. - * They are designed to be safer, more consistent, and less error prone replacements for strncpy. - */ - inline size_t strlcpy(char *dst, const char *src, size_t maxlen) - { - return indi_strlcpy(dst, src, maxlen); - } - - /** - * @brief The strlcpy() function copy strings respectively. - * They are designed to be safer, more consistent, and less error prone replacements for strncpy. - */ - template - inline size_t strlcpy(char (&dst)[N], const char *src) - { - return indi_strlcpy(dst, src, N); - } - -} -#endif diff --git a/src/core/base/libastro.cpp b/src/core/base/libastro.cpp deleted file mode 100644 index 97fb6eb2..00000000 --- a/src/core/base/libastro.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/* - libastro - - functions used for coordinate conversions, based on libnova - - Copyright (C) 2020 Chris Rowland - Copyright (C) 2021 Jasem Mutlaq - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this library; if not, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -*/ - -// file libastro.c -// -// holds extensions to the libnova library to provide useful functions for HYDROGEN -// -// Chris Rowland April 2020 -// - -#include "libastro.hpp" -#include "hydrogencom.hpp" - -#include - -#include -#include -#include -#include - -namespace HYDROGEN -{ - - ////////////////////////////////////////////////////////////////////////////////////////////// - // converts the Observed (JNow) position to a J2000 catalogue position by removing - // aberration, nutation and precession using the libnova library - ////////////////////////////////////////////////////////////////////////////////////////////// - void ObservedToJ2000(IEquatorialCoordinates *observed, double jd, IEquatorialCoordinates *J2000pos) - { - ln_equ_posn tempPos; - // RA Hours --> Degrees - struct ln_equ_posn libnova_observed = {observed->rightascension * 15.0, observed->declination}; - // remove the aberration - ln_get_equ_aber(&libnova_observed, jd, &tempPos); - // this conversion has added the aberration, we want to subtract it - tempPos.ra = libnova_observed.ra - (tempPos.ra - libnova_observed.ra); - tempPos.dec = libnova_observed.dec * 2 - tempPos.dec; - - // remove the nutation - ln_get_equ_nut(&tempPos, jd, true); - - struct ln_equ_posn libnova_J2000Pos; - // precess from now to J2000 - ln_get_equ_prec2(&tempPos, jd, JD2000, &libnova_J2000Pos); - - J2000pos->rightascension = libnova_J2000Pos.ra / 15.0; - J2000pos->declination = libnova_J2000Pos.dec; - } - - ////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief *J2000toObserved converts catalogue to observed - /// \param J2000pos catalogue position - /// \param jd julian day for the observed epoch - /// \param observed returns observed position - ////////////////////////////////////////////////////////////////////////////////////////////// - void J2000toObserved(IEquatorialCoordinates *J2000pos, double jd, IEquatorialCoordinates *observed) - { - ln_equ_posn tempPosn; - struct ln_equ_posn libnova_J2000Pos = {J2000pos->rightascension * 15.0, J2000pos->declination}; - - // apply precession from J2000 to jd - ln_get_equ_prec2(&libnova_J2000Pos, JD2000, jd, &tempPosn); - - // apply nutation - ln_get_equ_nut(&tempPosn, jd, false); - - struct ln_equ_posn libnova_observed; - // apply aberration - ln_get_equ_aber(&tempPosn, jd, &libnova_observed); - - observed->rightascension = libnova_observed.ra / 15.0; - observed->declination = libnova_observed.dec; - } - - ////////////////////////////////////////////////////////////////////////////////////////////// - /// apply or remove nutation - ////////////////////////////////////////////////////////////////////////////////////////////// - void ln_get_equ_nut(ln_equ_posn *posn, double jd, bool reverse) - { - // code lifted from libnova ln_get_equ_nut - // with the option to add or remove nutation - struct ln_nutation nut; - ln_get_nutation(jd, &nut); - - double mean_ra, mean_dec, delta_ra, delta_dec; - - mean_ra = DEG_TO_RAD(posn->ra); - mean_dec = DEG_TO_RAD(posn->dec); - - // Equ 22.1 - - double nut_ecliptic = DEG_TO_RAD(nut.ecliptic + nut.obliquity); - double sin_ecliptic = sin(nut_ecliptic); - - double sin_ra = sin(mean_ra); - double cos_ra = cos(mean_ra); - - double tan_dec = tan(mean_dec); - - delta_ra = (cos(nut_ecliptic) + sin_ecliptic * sin_ra * tan_dec) * nut.longitude - cos_ra * tan_dec * nut.obliquity; - delta_dec = (sin_ecliptic * cos_ra) * nut.longitude + sin_ra * nut.obliquity; - - // the sign changed to remove nutation - if (reverse) - { - delta_ra = -delta_ra; - delta_dec = -delta_dec; - } - posn->ra += delta_ra; - posn->dec += delta_dec; - } - - ////////////////////////////////////////////////////////////////////////////////////////////// - /// - ////////////////////////////////////////////////////////////////////////////////////////////// - void EquatorialToHorizontal(IEquatorialCoordinates *object, IGeographicCoordinates *observer, double JD, - IHorizontalCoordinates *position) - { - // Convert from HYDROGEN standard location to libnova standard location - struct ln_lnlat_posn libnova_location = {observer->longitude > 180 ? observer->longitude - 360 : observer->longitude, observer->latitude}; - // RA Hours --> Degrees - struct ln_equ_posn libnova_object = {object->rightascension * 15.0, object->declination}; - struct ln_hrz_posn horizontalPos; - ln_get_hrz_from_equ(&libnova_object, &libnova_location, JD, &horizontalPos); - position->azimuth = range360(180 + horizontalPos.az); - position->altitude = horizontalPos.alt; - } - - ////////////////////////////////////////////////////////////////////////////////////////////// - /// - ////////////////////////////////////////////////////////////////////////////////////////////// - void HorizontalToEquatorial(IHorizontalCoordinates *object, IGeographicCoordinates *observer, double JD, - IEquatorialCoordinates *position) - { - // Convert from HYDROGEN standard location to libnova standard location - struct ln_lnlat_posn libnova_location = {observer->longitude > 180 ? observer->longitude - 360 : observer->longitude, observer->latitude}; - // Convert from HYDROGEN standard location to libnova standard location - struct ln_hrz_posn libnova_object = {range360(object->azimuth + 180), object->altitude}; - struct ln_equ_posn equatorialPos; - ln_get_equ_from_hrz(&libnova_object, &libnova_location, JD, &equatorialPos); - // Degrees --> Hours - position->rightascension = equatorialPos.ra / 15.0; - position->declination = equatorialPos.dec; - } - -} diff --git a/src/core/base/libastro.hpp b/src/core/base/libastro.hpp deleted file mode 100644 index 5f8fdf70..00000000 --- a/src/core/base/libastro.hpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - libastro - - functions used for coordinate conversions, based on libnova - - Copyright (C) 2020 Chris Rowland - Copyright (C) 2021 Jasem Mutlaq - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this library; if not, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -*/ - -// file libsatro.h - -// functions used for coordinate conversions, based on libnova - -#pragma once - -#include - -namespace HYDROGEN -{ - -#define RAD_TO_DEG(rad) (rad * 180.0 / M_PI) -#define DEG_TO_RAD(deg) (deg * M_PI / 180.0) - - /** - * \defgroup Position Structures - - Structure for Celestial Equatorial, horizontal, and geographic positions. - */ - /*@{*/ - - /** \typedef IEquatorialCoordinates - \brief Celestial Equatorial Coordinates */ - typedef struct - { - double rightascension; /*!< Right Ascension in Hours (0 to 24)*/ - double declination; /*!< Delination in degrees (-90 to +90) */ - } IEquatorialCoordinates; - - /** \typedef IHorizontalCoordinates - \brief Topocentric Horizontal Coordinates */ - typedef struct - { - double azimuth; /*!< Azimuth in degrees (0 to 360 eastward. 0 North, 90 East, 180 South, 270 West)*/ - double altitude; /*!< Altitude in degrees (-90 to +90) */ - } IHorizontalCoordinates; - - /** \typedef IGeographicCoordinates - \brief Geographic Coordinates */ - typedef struct - { - double longitude; /*!< Longitude in degrees (0 to 360 eastward.)*/ - double latitude; /*!< Latitude in degrees (-90 to +90) */ - double elevation; /*!< Elevation from Mean Sea Level in meters */ - } IGeographicCoordinates; - - /*@}*/ - - /* - * \brief This provides astrometric helper functions - * based on the libnova library - */ - - /** - * \brief ObservedToJ2000 converts an observed position to a J2000 catalogue position - * removes aberration, nutation and precession - * \param observed position - * \param jd Julian day epoch of observed position - * \param J2000pos returns catalogue position - */ - void ObservedToJ2000(IEquatorialCoordinates *observed, double jd, IEquatorialCoordinates *J2000pos); - - /** - * \brief J2000toObserved converts a J2000 catalogue position to an observed position for the epoch jd - * applies precession, nutation and aberration - * \param J2000pos J2000 catalogue position - * \param jd Julian day epoch of observed position - * \param observed returns observed position for the JD epoch - */ - void J2000toObserved(IEquatorialCoordinates *J2000pos, double jd, IEquatorialCoordinates *observed); - - /** - * @brief EquatorialToHorizontal Calculate horizontal coordinates from equatorial coordinates. - * @param object Equatorial Object Coordinates in HYDROGEN standaard (RA Hours, DE degrees). - * @param observer Observer Location in HYDROGEN Standard (Longitude 0 to 360 Increasing Eastward) - * @param JD Julian Date - * @param position Calculated Horizontal Coordinates. - * @note Use this instead of libnova ln_get_hrz_from_equ since it corrects libnova Azimuth (0 = North and not South). - */ - void EquatorialToHorizontal(IEquatorialCoordinates *object, IGeographicCoordinates *observer, double JD, - IHorizontalCoordinates *position); - - /** - * @brief HorizontalToEquatorial Calculate Equatorial EOD Coordinates from horizontal coordinates - * @param object Horizontal Object Coordinates - * @param observer Observer Location in HYDROGEN Standard (Longitude 0 to 360 Increasing Eastward) - * @param JD Julian Date - * @param position Calculated Equatorial Coordinates in HYDROGEN standards (RA hours, DE degrees). - * @note Use this instead of libnova ln_get_equ_from_hrz since it corrects libnova Azimuth (0 = North and not South). - */ - void HorizontalToEquatorial(IHorizontalCoordinates *object, IGeographicCoordinates *observer, double JD, - IEquatorialCoordinates *position); - - /** - * \brief ln_get_equ_nut applies or removes nutation in place for the epoch JD - * \param posn position, nutation is applied or removed in place - * \param jd - * \param reverse set to true to remove nutation - */ - void ln_get_equ_nut(ln_equ_posn *posn, double jd, bool reverse = false); - -} diff --git a/src/core/base/lilxml.cpp b/src/core/base/lilxml.cpp deleted file mode 100644 index 65325945..00000000 --- a/src/core/base/lilxml.cpp +++ /dev/null @@ -1,1523 +0,0 @@ -#if 0 -liblilxml -Copyright (C) 2003 Elwood C. Downey - -This library is free software; -you can redistribute it and / or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; -either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; -without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; -if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA - -#endif - -/* little DOM-style XML parser. - * only handles elements, attributes and pcdata content. - * and are silently ignored. - * pcdata is collected into one string, sans leading whitespace first line. - * - * #define MAIN_TST to create standalone test program - */ - -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) -#define snprintf _snprintf -#pragma warning(push) -///@todo Introduce plattform indipendent safe functions as macros to fix this -#pragma warning(disable : 4996) -#endif - -#include "lilxml.hpp" - -/* used to efficiently manage growing malloced string space */ -typedef struct -{ - char *s; /* malloced memory for string */ - int sl; /* string length, sans trailing \0 */ - int sm; /* total malloced bytes */ -} String; -#define MINMEM 64 /* starting string length */ - -static int oneXMLchar(LilXML *lp, int c, char ynot[]); -static void initParser(LilXML *lp); -static void pushXMLEle(LilXML *lp); -static void popXMLEle(LilXML *lp); -static void resetEndTag(LilXML *lp); -static XMLAtt *growAtt(XMLEle *e); -static XMLEle *growEle(XMLEle *pe); -static void freeAtt(XMLAtt *a); -static int isTokenChar(int start, int c); -static void growString(String *sp, int c); -static void appendString(String *sp, const char *str); -static void freeString(String *sp); -static void newString(String *sp); -static void *moremem(void *old, size_t n); -static void appXMLEle(XMLEle *ep, XMLEle *newep); - -typedef enum -{ - LOOK4START = 0, /* looking for first element start */ - LOOK4TAG, /* looking for element tag */ - INTAG, /* reading tag */ - LOOK4ATTRN, /* looking for attr name, > or / */ - INATTRN, /* reading attr name */ - LOOK4ATTRV, /* looking for attr value */ - SAWSLASH, /* saw / in element opening */ - INATTRV, /* in attr value */ - ENTINATTRV, /* in entity in attr value */ - LOOK4CON, /* skipping leading content whitespc */ - INCON, /* reading content */ - ENTINCON, /* in entity in pcdata */ - SAWLTINCON, /* saw < in content */ - LOOK4CLOSETAG, /* looking for closing tag after < */ - INCLOSETAG /* reading closing tag */ -} State; /* parsing states */ - -/* maintain state while parsing */ -struct LilXML_ -{ - State cs; /* current state */ - int ln; /* line number for diags */ - XMLEle *ce; /* current element being built */ - String endtag; /* to check for match with opening tag*/ - String entity; /* collect entity seq */ - int delim; /* attribute value delimiter */ - int lastc; /* last char (just used wiht skipping)*/ - int skipping; /* in comment or declaration */ - int inblob; /* in oneBLOB element */ -}; - -/* internal representation of a (possibly nested) XML element */ -struct xml_ele_ -{ - String tag; /* element tag */ - XMLEle *pe; /* parent element, or NULL if root */ - XMLAtt **at; /* list of attributes */ - int nat; /* number of attributes */ - int ait; /* used to iterate over at[] */ - XMLEle **el; /* list of child elements */ - int nel; /* number of child elements */ - int eit; /* used to iterate over el[] */ - String pcdata; /* character data in this element */ - int pcdata_hasent; /* 1 if pcdata contains an entity char*/ -}; - -/* internal representation of an attribute */ -struct xml_att_ -{ - String name; /* name */ - String valu; /* value */ - XMLEle *ce; /* containing element */ -}; - -/* characters that need escaping as "entities" in attr values and pcdata - */ -static char entities[] = "&<>'\""; - -/* default memory managers, override with lilxmlMalloc() */ -static void *(*mymalloc)(size_t size) = malloc; -static void *(*myrealloc)(void *ptr, size_t size) = realloc; -static void (*myfree)(void *ptr) = free; - -/* install new version of malloc/realloc/free. - * N.B. don't call after first use of any other lilxml function - */ -void lilxmlMalloc(void *(*newmalloc)(size_t size), void *(*newrealloc)(void *ptr, size_t size), - void (*newfree)(void *ptr)) -{ - mymalloc = newmalloc; - myrealloc = newrealloc; - myfree = newfree; -} - -/* pass back a fresh handle for use with our other functions */ -LilXML *newLilXML() -{ - LilXML *lp = (LilXML *)moremem(NULL, sizeof *lp); - memset(lp, 0, sizeof *lp); - initParser(lp); - return (lp); -} - -/* discard */ -void delLilXML(LilXML *lp) -{ - delXMLEle(lp->ce); - freeString(&lp->endtag); - (*myfree)(lp); -} - -/* delete ep and all its children and remove from parent's list if known */ -void delXMLEle(XMLEle *ep) -{ - int i; - - /* benign if NULL */ - if (!ep) - return; - - /* delete all parts of ep */ - freeString(&ep->tag); - freeString(&ep->pcdata); - if (ep->at) - { - for (i = 0; i < ep->nat; i++) - freeAtt(ep->at[i]); - (*myfree)(ep->at); - } - if (ep->el) - { - for (i = 0; i < ep->nel; i++) - { - /* forget parent so deleting doesn't modify _this_ el[] */ - ep->el[i]->pe = NULL; - - delXMLEle(ep->el[i]); - } - (*myfree)(ep->el); - } - - /* remove from parent's list if known */ - if (ep->pe) - { - XMLEle *pe = ep->pe; - for (i = 0; i < pe->nel; i++) - { - if (pe->el[i] == ep) - { - memmove(&pe->el[i], &pe->el[i + 1], (--pe->nel - i) * sizeof(XMLEle *)); - break; - } - } - } - - /* delete ep itself */ - (*myfree)(ep); -} - -// #define WITH_MEMCHR -XMLEle **parseXMLChunk(LilXML *lp, char *buf, int size, char ynot[]) -{ - unsigned int nnodes = 1; - XMLEle **nodes = (XMLEle **)malloc(nnodes * sizeof *nodes); - *nodes = NULL; - char *curr = buf; - int s; - ynot[0] = '\0'; - - if (lp->inblob) - { -#ifdef WITH_ENCLEN - if (size < lp->ce->pcdata.sm - lp->ce->pcdata.sl) - { - memcpy((void *)(lp->ce->pcdata.s + lp->ce->pcdata.sl), (const void *)buf, size); - lp->ce->pcdata.sl += size; - return nodes; - } - else - lp->inblob = 0; -#endif -#ifdef WITH_MEMCHR - char *ltpos = memchr(buf, '<', size); - if (!ltpos) - { - lp->ce->pcdata.s = (char *)moremem(lp->ce->pcdata.s, lp->ce->pcdata.sm + size); - lp->ce->pcdata.sm += size; - memcpy((void *)(lp->ce->pcdata.s + lp->ce->pcdata.sl), (const void *)buf, size); - lp->ce->pcdata.sl += size; - return nodes; - } - else - lp->inblob = 0; -#endif - } - else - { - if (lp->ce) - { - char *ctag = tagXMLEle(lp->ce); - if (ctag && !(strcmp(ctag, "oneBLOB")) && (lp->cs == INCON)) - { -#ifdef WITH_ENCLEN - XMLAtt *blenatt = findXMLAtt(lp->ce, "enclen"); - if (blenatt) - { - int blen; - sscanf(valuXMLAtt(blenatt), "%d", &blen); - - // Add room for those '\n' on every 72 character line + extra half-full line. - blen += (blen / 72) + 1; - - lp->ce->pcdata.s = (char *)moremem(lp->ce->pcdata.s, blen); - lp->ce->pcdata.sm = blen; // always set sm - - if (size <= blen - lp->ce->pcdata.sl) - { - memcpy((void *)(lp->ce->pcdata.s + lp->ce->pcdata.sl), (const void *)buf, size); - lp->ce->pcdata.sl += size; - lp->inblob = 1; - return nodes; - } - } -#endif -#ifdef WITH_MEMCHR - char *ltpos = memchr(buf, '<', size); - if (!ltpos) - { - lp->ce->pcdata.s = (char *)moremem(lp->ce->pcdata.s, lp->ce->pcdata.sm + size); - lp->ce->pcdata.sm += size; - memcpy((void *)(lp->ce->pcdata.s + lp->ce->pcdata.sl), (const void *)buf, size); - lp->ce->pcdata.sl += size; - lp->inblob = 1; - return nodes; - } - else - lp->inblob = 0; -#endif - } - } - } - while (curr - buf < size) - { - char newc = *curr; - /* EOF? */ - if (newc == 0) - { - sprintf(ynot, "Line %d: early XML EOF", lp->ln); - initParser(lp); - curr++; - continue; - } - - /* new line? */ - if (newc == '\n') - lp->ln++; - - /* skip comments and declarations. requires 1 char history */ - if (!lp->skipping && lp->lastc == '<' && (newc == '?' || newc == '!')) - { - lp->skipping = 1; - lp->lastc = newc; - curr++; - continue; - } - if (lp->skipping) - { - if (newc == '>') - lp->skipping = 0; - lp->lastc = newc; - curr++; - continue; - } - if (newc == '<') - { - lp->lastc = '<'; - curr++; - continue; - } - - /* do a pending '<' first then newc */ - if (lp->lastc == '<') - { - if (oneXMLchar(lp, '<', ynot) < 0) - { - initParser(lp); - curr++; - continue; - } - /* N.B. we assume '<' will never result in closure */ - } - - /* process newc (at last!) */ - s = oneXMLchar(lp, newc, ynot); - if (s == 0) - { - lp->lastc = newc; - curr++; - continue; - } - if (s < 0) - { - initParser(lp); - curr++; - continue; - } - - /* Ok! store ce in nodes and we start over. - * N.B. up to caller to call delXMLEle with what we return. - */ - nodes[nnodes - 1] = lp->ce; - nodes = (XMLEle **)realloc(nodes, (nnodes + 1) * sizeof *nodes); - nodes[nnodes] = NULL; - nnodes += 1; - lp->ce = NULL; - initParser(lp); - curr++; - } - /* - * N.B. up to caller to free nodes. - */ - return nodes; -} - -/* process one more character of an XML file. - * when find closure with outter element return root of complete tree. - * when find error return NULL with reason in ynot[]. - * when need more return NULL with ynot[0] = '\0'. - * N.B. it is up to the caller to delete any tree returned with delXMLEle(). - */ -XMLEle *readXMLEle(LilXML *lp, int newc, char ynot[]) -{ - XMLEle *root; - int s; - - /* start optimistic */ - ynot[0] = '\0'; - - /* EOF? */ - if (newc == 0) - { - sprintf(ynot, "Line %d: early XML EOF", lp->ln); - initParser(lp); - return (NULL); - } - - /* new line? */ - if (newc == '\n') - lp->ln++; - - /* skip comments and declarations. requires 1 char history */ - if (!lp->skipping && lp->lastc == '<' && (newc == '?' || newc == '!')) - { - lp->skipping = 1; - lp->lastc = newc; - return (NULL); - } - if (lp->skipping) - { - if (newc == '>') - lp->skipping = 0; - lp->lastc = newc; - return (NULL); - } - if (newc == '<') - { - lp->lastc = '<'; - return (NULL); - } - - /* do a pending '<' first then newc */ - if (lp->lastc == '<') - { - if (oneXMLchar(lp, '<', ynot) < 0) - { - initParser(lp); - return (NULL); - } - /* N.B. we assume '<' will never result in closure */ - } - - /* process newc (at last!) */ - s = oneXMLchar(lp, newc, ynot); - if (s == 0) - { - lp->lastc = newc; - return (NULL); - } - if (s < 0) - { - initParser(lp); - return (NULL); - } - - /* Ok! return ce and we start over. - * N.B. up to caller to call delXMLEle with what we return. - */ - root = lp->ce; - lp->ce = NULL; - initParser(lp); - return (root); -} - -/* parse the given XML string. - * return XMLEle* else NULL with reason why in ynot[] - */ -XMLEle *parseXML(char buf[], char ynot[]) -{ - LilXML *lp = newLilXML(); - XMLEle *root; - - do - { - root = readXMLEle(lp, *buf++, ynot); - } while (!root && !ynot[0]); - - delLilXML(lp); - - return (root); -} - -/* return a deep copy of the given XMLEle * - */ -XMLEle *cloneXMLEle(XMLEle *ep) -{ - char *buf; - char ynot[1024]; - XMLEle *newep; - - buf = (char *)(*mymalloc)(sprlXMLEle(ep, 0) + 1); - sprXMLEle(buf, ep, 0); - newep = parseXML(buf, ynot); - (*myfree)(buf); - - return (newep); -} - -XMLEle *cloneXMLEle(XMLEle *ep, int (*replace)(void *self, XMLEle *source, XMLEle **replace), void *self) -{ - XMLEle *result = nullptr; - if (replace && (*replace)(self, ep, &result)) - { - return result; - } - result = shallowCloneXMLEle(ep); - - for (int i = 0; i < ep->nel; ++i) - { - XMLEle *child = ep->el[i]; - XMLEle *repl = cloneXMLEle(child, replace, self); - if (repl != nullptr) - { - repl->pe = result; - appXMLEle(result, repl); - } - } - - if (pcdatalenXMLEle(ep)) - { - editXMLEle(result, pcdataXMLEle(ep)); - } - - return result; -} - -/* search ep for an attribute with given name. - * return NULL if not found. - */ -XMLAtt *findXMLAtt(XMLEle *ep, const char *name) -{ - int i; - - for (i = 0; i < ep->nat; i++) - if (!strcmp(ep->at[i]->name.s, name)) - return (ep->at[i]); - return (NULL); -} - -/* search ep for an element with given tag. - * return NULL if not found. - */ -XMLEle *findXMLEle(XMLEle *ep, const char *tag) -{ - int tl = (int)strlen(tag); - int i; - - for (i = 0; i < ep->nel; i++) - { - String *sp = &ep->el[i]->tag; - if (sp->sl == tl && !strcmp(sp->s, tag)) - return (ep->el[i]); - } - return (NULL); -} - -/* iterate over each child element of ep. - * call first time with first set to 1, then 0 from then on. - * returns NULL when no more or err - */ -XMLEle *nextXMLEle(XMLEle *ep, int init) -{ - int eit; - - if (init) - ep->eit = 0; - - eit = ep->eit++; - if (eit < 0 || eit >= ep->nel) - return (NULL); - return (ep->el[eit]); -} - -/* iterate over each attribute of ep. - * call first time with first set to 1, then 0 from then on. - * returns NULL when no more or err - */ -XMLAtt *nextXMLAtt(XMLEle *ep, int init) -{ - int ait; - - if (init) - ep->ait = 0; - - ait = ep->ait++; - if (ait < 0 || ait >= ep->nat) - return (NULL); - return (ep->at[ait]); -} - -/* return parent of given XMLEle */ -XMLEle *parentXMLEle(XMLEle *ep) -{ - return (ep->pe); -} - -/* return parent element of given XMLAtt */ -XMLEle *parentXMLAtt(XMLAtt *ap) -{ - return (ap->ce); -} - -/* access functions */ - -/* return the tag name of the given element */ -char *tagXMLEle(XMLEle *ep) -{ - return (ep->tag.s); -} - -/* return the pcdata portion of the given element */ -char *pcdataXMLEle(XMLEle *ep) -{ - return (ep->pcdata.s); -} - -/* return the number of characters in the pcdata portion of the given element */ -int pcdatalenXMLEle(XMLEle *ep) -{ - return (ep->pcdata.sl); -} - -/* return the name of the given attribute */ -char *nameXMLAtt(XMLAtt *ap) -{ - return (ap->name.s); -} - -/* return the value of the given attribute */ -char *valuXMLAtt(XMLAtt *ap) -{ - return (ap->valu.s); -} - -/* return the number of child elements of the given element */ -int nXMLEle(XMLEle *ep) -{ - return (ep->nel); -} - -/* return the number of attributes in the given element */ -int nXMLAtt(XMLEle *ep) -{ - return (ep->nat); -} - -/* search ep for an attribute with the given name and return its value. - * return "" if not found. - */ -const char *findXMLAttValu(XMLEle *ep, const char *name) -{ - XMLAtt *a = findXMLAtt(ep, name); - return (a ? a->valu.s : ""); -} - -/* handy wrapper to read one xml file. - * return root element else NULL with report in ynot[] - */ -XMLEle *readXMLFile(FILE *fp, LilXML *lp, char ynot[]) -{ - int c; - - while ((c = fgetc(fp)) != EOF) - { - XMLEle *root = readXMLEle(lp, c, ynot); - if (root || ynot[0]) - return (root); - } - - return (NULL); -} - -/* add an element with the given tag to the given element. - * parent can be NULL to make a new root. - */ -XMLEle *addXMLEle(XMLEle *parent, const char *tag) -{ - XMLEle *ep = growEle(parent); - appendString(&ep->tag, tag); - return (ep); -} - -/* append an existing element to the given element. - * N.B. be mindful of when these are deleted, this is not a deep copy. - */ -static void appXMLEle(XMLEle *ep, XMLEle *newep) -{ - ep->el = (XMLEle **)moremem(ep->el, (ep->nel + 1) * sizeof(XMLEle *)); - ep->el[ep->nel++] = newep; -} - -/* Update the tag of an element - */ -XMLEle *setXMLEleTag(XMLEle *ep, const char *tag) -{ - freeString(&ep->tag); - newString(&ep->tag); - appendString(&ep->tag, tag); - return ep; -} - -/* set the pcdata of the given element */ -void editXMLEle(XMLEle *ep, const char *pcdata) -{ - freeString(&ep->pcdata); - appendString(&ep->pcdata, pcdata); - ep->pcdata_hasent = (strpbrk(pcdata, entities) != NULL); -} - -/* add an attribute to the given XML element */ -XMLAtt *addXMLAtt(XMLEle *ep, const char *name, const char *valu) -{ - XMLAtt *ap = growAtt(ep); - appendString(&ap->name, name); - appendString(&ap->valu, valu); - return (ap); -} - -/* remove the named attribute from ep, if any */ -void rmXMLAtt(XMLEle *ep, const char *name) -{ - int i; - - for (i = 0; i < ep->nat; i++) - { - if (strcmp(ep->at[i]->name.s, name) == 0) - { - freeAtt(ep->at[i]); - memmove(&ep->at[i], &ep->at[i + 1], (--ep->nat - i) * sizeof(XMLAtt *)); - return; - } - } -} - -/* Copy a node, without any child (nor cdata) */ -XMLEle *shallowCloneXMLEle(XMLEle *ele) -{ - XMLEle *repl = addXMLEle(nullptr, tagXMLEle(ele)); - - for (int i = 0; i < ele->nat; ++i) - { - auto att = ele->at[i]; - addXMLAtt(repl, nameXMLAtt(att), valuXMLAtt(att)); - } - - return repl; -} - -/* change the value of an attribute to str */ -void editXMLAtt(XMLAtt *ap, const char *str) -{ - freeString(&ap->valu); - appendString(&ap->valu, str); -} - -#define PRINDENT 4 /* sample print indent each level */ -#define PRINDENTSTR " " /* sample print indent each level */ - -/* Abstract class for XML to string convertion */ -class XMLOutput -{ -protected: - XMLOutput() {} - virtual ~XMLOutput(){}; - -protected: - virtual void cdataCb(XMLEle *ele) - { - (void)ele; - }; - -public: - virtual void put(const char *str, size_t len) = 0; - void put(const char *str) - { - put(str, strlen(str)); - }; - void indent(int indent) - { - for (int i = 0; i < indent; ++i) - put(PRINDENTSTR, PRINDENT); - } - void putEntityXML(const char *str); - - /* output a XML node */ - void putXML(XMLEle *el, int level); -}; - -void XMLOutput::putXML(XMLEle *ep, int level) -{ - int i; - indent(level); - put("<"); - put(ep->tag.s); - - for (i = 0; i < ep->nat; i++) - { - put(" "); - put(ep->at[i]->name.s); - put("=\""); - putEntityXML(ep->at[i]->valu.s); - put("\""); - } - - if (ep->nel > 0) - { - put(">\n"); - for (i = 0; i < ep->nel; i++) - putXML(ep->el[i], level + 1); - } - if (ep->pcdata.sl > 0) - { - if (ep->nel == 0) - put(">\n"); - // Declare the cdata offset - cdataCb(ep); - if (ep->pcdata_hasent) - putEntityXML(ep->pcdata.s); - else - put(ep->pcdata.s); - if (ep->pcdata.s[ep->pcdata.sl - 1] != '\n') - put("\n"); - } - if (ep->nel > 0 || ep->pcdata.sl > 0) - { - indent(level); - put("tag.s); - put(">\n"); - } - else - put("/>\n"); -} - -class FileXMLOutput : public XMLOutput -{ - FILE *file; - -public: - FileXMLOutput(FILE *f) : XMLOutput(), file(f){}; - virtual ~FileXMLOutput(){}; - virtual void put(const char *str, size_t len) - { - fwrite(str, len, 1, file); - } -}; - -/* sample print ep to fp - * N.B. set level = 0 on first call - */ -void prXMLEle(FILE *fp, XMLEle *ep, int level) -{ - FileXMLOutput fpo(fp); - fpo.putXML(ep, level); -} - -/* XML Output to a memory buffer */ -class BufferXMLOutput : public XMLOutput -{ - char *buffer; - size_t offset; - -public: - BufferXMLOutput(char *buffer) : XMLOutput(), buffer(buffer), offset(0){}; - virtual ~BufferXMLOutput(){}; - virtual void put(const char *str, size_t len) - { - memcpy(buffer + offset, str, len); - offset += len; - } - size_t size() - { - return offset; - } -}; - -/* sample print ep to string s. - * N.B. s must be at least as large as that reported by sprlXMLEle()+1. - * N.B. set level = 0 on first call - * return length of resulting string (sans trailing \0) - */ -size_t sprXMLEle(char *s, XMLEle *ep, int level) -{ - BufferXMLOutput bxo(s); - bxo.putXML(ep, level); - return bxo.size(); -} - -/* An output that just count chars (for sizing/locating elements) */ -class NullXMLOutput : public XMLOutput -{ - size_t offset; - XMLEle *cdataWatch; - size_t cdataOffset; - -protected: - virtual void cdataCb(XMLEle *ele) - { - if (ele == cdataWatch && cdataWatch) - { - cdataOffset = offset; - } - } - -public: - NullXMLOutput() : XMLOutput(), offset(0), cdataWatch(nullptr), cdataOffset((size_t)-1){}; - virtual ~NullXMLOutput(){}; - virtual void put(const char *str, size_t len) - { - (void)str; - offset += len; - } - size_t size() - { - return offset; - } - - void setCdataWatch(XMLEle *ele) - { - cdataWatch = ele; - } - size_t cdataFound() - { - return cdataOffset; - } -}; - -/* return number of bytes in a string guaranteed able to hold result of - * sprXLMEle(ep) (sans trailing \0). - * N.B. set level = 0 on first call - */ -size_t sprlXMLEle(XMLEle *ep, int level) -{ - NullXMLOutput bxo; - bxo.putXML(ep, level); - return bxo.size(); -} - -/* Return the exact starting offset of the CDATA of ep in the printed representation */ -size_t sprXMLCDataOffset(XMLEle *root, XMLEle *ep, int level) -{ - NullXMLOutput bxo; - bxo.setCdataWatch(ep); - bxo.putXML(root, level); - return bxo.cdataFound(); -} - -void XMLOutput::putEntityXML(const char *s) -{ - const char *ep = NULL; - for (; (ep = strpbrk(s, entities)) != NULL; s = ep + 1) - { - /* found another entity, copy preceding to malloced buffer */ - size_t nnew = size_t(ep - s); /* all but entity itself */ - put(s, nnew); - - /* replace with entity encoding */ - switch (*ep) - { - case '&': - put("&"); - break; - case '<': - put("<"); - break; - case '>': - put(">"); - break; - case '\'': - put("'"); - break; - case '"': - put("""); - break; - } - } - - put(s); -} - -/* return a string with all xml-sensitive characters within the passed string s - * replaced with their entity sequence equivalents. - * N.B. caller must use the returned string before calling us again. - */ -char *entityXML(char *s) -{ - // FIXME: this is not thread safe. Signature must be changed (deprecate ?) - static char *malbuf; - size_t nmalbuf = 0; - char *sret = NULL; - char *ep = NULL; - - /* scan for each entity, if any */ - for (sret = s; (ep = strpbrk(s, entities)) != NULL; s = ep + 1) - { - /* found another entity, copy preceding to malloced buffer */ - size_t nnew = size_t(ep - s); /* all but entity itself */ - sret = malbuf = (char *)moremem(malbuf, nmalbuf + nnew + 10); - memcpy(malbuf + nmalbuf, s, nnew); - nmalbuf += nnew; - - /* replace with entity encoding */ - switch (*ep) - { - case '&': - nmalbuf += sprintf(malbuf + nmalbuf, "&"); - break; - case '<': - nmalbuf += sprintf(malbuf + nmalbuf, "<"); - break; - case '>': - nmalbuf += sprintf(malbuf + nmalbuf, ">"); - break; - case '\'': - nmalbuf += sprintf(malbuf + nmalbuf, "'"); - break; - case '"': - nmalbuf += sprintf(malbuf + nmalbuf, """); - break; - } - } - - /* return s if no entities, else malloc cleaned-up copy */ - if (sret == s) - { - /* using s, so free any alloced memory from last time */ - if (malbuf) - { - free(malbuf); - malbuf = NULL; - } - return s; - } - else - { - /* put remaining part of s into malbuf */ - size_t nleft = strlen(s) + 1; /* include \0 */ - sret = malbuf = (char *)moremem(malbuf, nmalbuf + nleft); - memcpy(malbuf + nmalbuf, s, nleft); - } - - return (sret); -} - -/* if ent is a recognized xml entity sequence, set *cp to char and return 1 - * else return 0 - */ -static int decodeEntity(char *ent, int *cp) -{ - static struct - { - const char *ent; - char c; - } enttable[] = - { - {"&", '&'}, - {"'", '\''}, - {"<", '<'}, - {">", '>'}, - {""", '"'}, - }; - for (size_t i = 0; i < (sizeof(enttable) / sizeof(enttable[0])); i++) - { - if (strcmp(ent, enttable[i].ent) == 0) - { - *cp = enttable[i].c; - return (1); - } - } - - return (0); -} - -/* process one more char in XML file. - * if find final closure, return 1 and tree is in ce. - * if need more, return 0. - * if real trouble, return -1 and put reason in ynot. - */ -static int oneXMLchar(LilXML *lp, int c, char ynot[]) -{ - switch (lp->cs) - { - case LOOK4START: /* looking for first element start */ - if (c == '<') - { - pushXMLEle(lp); - lp->cs = LOOK4TAG; - } - /* silently ignore until resync */ - break; - - case LOOK4TAG: /* looking for element tag */ - if (isTokenChar(1, c)) - { - growString(&lp->ce->tag, c); - lp->cs = INTAG; - } - else if (!isspace(c)) - { - sprintf(ynot, "Line %d: Bogus tag char %c", lp->ln, c); - return (-1); - } - break; - - case INTAG: /* reading tag */ - if (isTokenChar(0, c)) - growString(&lp->ce->tag, c); - else if (c == '>') - lp->cs = LOOK4CON; - else if (c == '/') - lp->cs = SAWSLASH; - else - lp->cs = LOOK4ATTRN; - break; - - case LOOK4ATTRN: /* looking for attr name, > or / */ - if (c == '>') - lp->cs = LOOK4CON; - else if (c == '/') - lp->cs = SAWSLASH; - else if (isTokenChar(1, c)) - { - XMLAtt *ap = growAtt(lp->ce); - growString(&ap->name, c); - lp->cs = INATTRN; - } - else if (!isspace(c)) - { - sprintf(ynot, "Line %d: Bogus leading attr name char: %c", lp->ln, c); - return (-1); - } - break; - - case SAWSLASH: /* saw / in element opening */ - if (c == '>') - { - if (!lp->ce->pe) - return (1); /* root has no content */ - popXMLEle(lp); - lp->cs = LOOK4CON; - } - else - { - sprintf(ynot, "Line %d: Bogus char %c before >", lp->ln, c); - return (-1); - } - break; - - case INATTRN: /* reading attr name */ - if (isTokenChar(0, c)) - growString(&lp->ce->at[lp->ce->nat - 1]->name, c); - else if (isspace(c) || c == '=') - lp->cs = LOOK4ATTRV; - else - { - sprintf(ynot, "Line %d: Bogus attr name char: %c", lp->ln, c); - return (-1); - } - break; - - case LOOK4ATTRV: /* looking for attr value */ - if (c == '\'' || c == '"') - { - lp->delim = c; - lp->cs = INATTRV; - } - else if (!(isspace(c) || c == '=')) - { - sprintf(ynot, "Line %d: No value for attribute %s", lp->ln, lp->ce->at[lp->ce->nat - 1]->name.s); - return (-1); - } - break; - - case INATTRV: /* in attr value */ - if (c == '&') - { - newString(&lp->entity); - growString(&lp->entity, c); - lp->cs = ENTINATTRV; - } - else if (c == lp->delim) - lp->cs = LOOK4ATTRN; - else if (!iscntrl(c)) - growString(&lp->ce->at[lp->ce->nat - 1]->valu, c); - break; - - case ENTINATTRV: /* working on entity in attr valu */ - if (c == ';') - { - /* if find a recongized esp seq, add equiv char else raw seq */ - growString(&lp->entity, c); - if (decodeEntity(lp->entity.s, &c)) - growString(&lp->ce->at[lp->ce->nat - 1]->valu, c); - else - appendString(&lp->ce->at[lp->ce->nat - 1]->valu, lp->entity.s); - freeString(&lp->entity); - lp->cs = INATTRV; - } - else - growString(&lp->entity, c); - break; - - case LOOK4CON: /* skipping leading content whitespace*/ - if (c == '<') - lp->cs = SAWLTINCON; - else if (!isspace(c)) - { - growString(&lp->ce->pcdata, c); - lp->cs = INCON; - } - break; - - case INCON: /* reading content */ - if (c == '&') - { - newString(&lp->entity); - growString(&lp->entity, c); - lp->cs = ENTINCON; - } - else if (c == '<') - { - /* chomp trailing whitespace */ - while (lp->ce->pcdata.sl > 0 && isspace(lp->ce->pcdata.s[lp->ce->pcdata.sl - 1])) - lp->ce->pcdata.s[--(lp->ce->pcdata.sl)] = '\0'; - lp->cs = SAWLTINCON; - } - else - { - growString(&lp->ce->pcdata, c); - } - break; - - case ENTINCON: /* working on entity in content */ - if (c == ';') - { - /* if find a recognized esc seq, add equiv char else raw seq */ - growString(&lp->entity, c); - if (decodeEntity(lp->entity.s, &c)) - growString(&lp->ce->pcdata, c); - else - { - appendString(&lp->ce->pcdata, lp->entity.s); - // lp->ce->pcdata_hasent = 1; - } - // JM 2018-09-26: Even if decoded, we always set - // pcdata_hasent to 1 since we need to encode it again - // before sending it over to clients and drivers. - lp->ce->pcdata_hasent = 1; - freeString(&lp->entity); - lp->cs = INCON; - } - else - growString(&lp->entity, c); - break; - - case SAWLTINCON: /* saw < in content */ - if (c == '/') - { - resetEndTag(lp); - lp->cs = LOOK4CLOSETAG; - } - else - { - pushXMLEle(lp); - if (isTokenChar(1, c)) - { - growString(&lp->ce->tag, c); - lp->cs = INTAG; - } - else - lp->cs = LOOK4TAG; - } - break; - - case LOOK4CLOSETAG: /* looking for closing tag after < */ - if (isTokenChar(1, c)) - { - growString(&lp->endtag, c); - lp->cs = INCLOSETAG; - } - else if (!isspace(c)) - { - sprintf(ynot, "Line %d: Bogus preend tag char %c", lp->ln, c); - return (-1); - } - break; - - case INCLOSETAG: /* reading closing tag */ - if (isTokenChar(0, c)) - growString(&lp->endtag, c); - else if (c == '>') - { - if (strcmp(lp->ce->tag.s, lp->endtag.s)) - { - sprintf(ynot, "Line %d: closing tag %s does not match %s", lp->ln, lp->endtag.s, lp->ce->tag.s); - return (-1); - } - else if (lp->ce->pe) - { - popXMLEle(lp); - lp->cs = LOOK4CON; /* back to content after nested elem */ - } - else - return (1); /* yes! */ - } - else if (!isspace(c)) - { - sprintf(ynot, "Line %d: Bogus end tag char %c", lp->ln, c); - return (-1); - } - break; - } - - return (0); -} - -/* set up for a fresh start again */ -static void initParser(LilXML *lp) -{ - delXMLEle(lp->ce); - freeString(&lp->endtag); - memset(lp, 0, sizeof(*lp)); - newString(&lp->endtag); - lp->cs = LOOK4START; - lp->ln = 1; -} - -/* start a new XMLEle. - * point ce to a new XMLEle. - * if ce already set up, add to its list of child elements too. - * endtag no longer valid. - */ -static void pushXMLEle(LilXML *lp) -{ - lp->ce = growEle(lp->ce); - resetEndTag(lp); -} - -/* point ce to parent of current ce. - * endtag no longer valid. - */ -static void popXMLEle(LilXML *lp) -{ - lp->ce = lp->ce->pe; - resetEndTag(lp); -} - -/* return one new XMLEle, added to the given element if given */ -static XMLEle *growEle(XMLEle *pe) -{ - XMLEle *newe = (XMLEle *)moremem(NULL, sizeof(XMLEle)); - - memset(newe, 0, sizeof(XMLEle)); - newString(&newe->tag); - newString(&newe->pcdata); - newe->pe = pe; - - if (pe) - { - pe->el = (XMLEle **)moremem(pe->el, (pe->nel + 1) * sizeof(XMLEle *)); - pe->el[pe->nel++] = newe; - } - - return (newe); -} - -/* add room for and return one new XMLAtt to the given element */ -static XMLAtt *growAtt(XMLEle *ep) -{ - XMLAtt *newa = (XMLAtt *)moremem(NULL, sizeof *newa); - - memset(newa, 0, sizeof(*newa)); - newString(&newa->name); - newString(&newa->valu); - newa->ce = ep; - - ep->at = (XMLAtt **)moremem(ep->at, (ep->nat + 1) * sizeof(XMLAtt *)); - ep->at[ep->nat++] = newa; - - return (newa); -} - -/* free a and all it holds */ -static void freeAtt(XMLAtt *a) -{ - if (!a) - return; - freeString(&a->name); - freeString(&a->valu); - (*myfree)(a); -} - -/* reset endtag */ -static void resetEndTag(LilXML *lp) -{ - freeString(&lp->endtag); - newString(&lp->endtag); -} - -/* 1 if c is a valid token character, else 0. - * it can be alpha or '_' or numeric unless start. - */ -static int isTokenChar(int start, int c) -{ - return (isalpha(c) || c == '_' || (!start && isdigit(c))); -} - -/* grow the String storage at *sp to append c */ -static void growString(String *sp, int c) -{ - int l = sp->sl + 2; /* need room for '\0' plus c */ - - if (l > sp->sm) - { - if (!sp->s) - newString(sp); - else - { - sp->s = (char *)moremem(sp->s, sp->sm *= 2); - } - } - sp->s[--l] = '\0'; - sp->s[--l] = (char)c; - sp->sl++; -} - -/* append str to the String storage at *sp */ -static void appendString(String *sp, const char *str) -{ - if (!sp || !str) - return; - - int strl = int(strlen(str)); - int l = sp->sl + strl + 1; /* need room for '\0' */ - - if (l > sp->sm) - { - if (!sp->s) - newString(sp); - if (l > sp->sm) - { - sp->s = (char *)moremem(sp->s, (sp->sm = l)); - } - } - if (sp->s) - { - strcpy(&sp->s[sp->sl], str); - sp->sl += strl; - } -} - -/* init a String with a malloced string containing just \0 */ -static void newString(String *sp) -{ - if (!sp) - return; - - sp->s = (char *)moremem(NULL, MINMEM); - sp->sm = MINMEM; - *sp->s = '\0'; - sp->sl = 0; -} - -/* free memory used by the given String */ -static void freeString(String *sp) -{ - if (sp->s) - (*myfree)(sp->s); - sp->s = NULL; - sp->sl = 0; - sp->sm = 0; -} - -/* like malloc but knows to use realloc if already started */ -static void *moremem(void *old, size_t n) -{ - void *p = (old ? (*myrealloc)(old, n) : (*mymalloc)(n)); - if (p == 0) - { - fprintf(stderr, "%s(%s): Failed to allocate memory.\n", __FILE__, __func__); - exit(1); - } - return p; -} - -#if defined(MAIN_TST) -int main(int ac, char *av[]) -{ - LilXML *lp = newLilXML(); - char ynot[1024]; - XMLEle *root; - - root = readXMLFile(stdin, lp, ynot); - if (root) - { - char *str; - int l; - - if (ac > 1) - { - XMLEle *theend = addXMLEle(root, "theend"); - editXMLEle(theend, "Added to test editing"); - addXMLAtt(theend, "hello", "world"); - } - - fprintf(stderr, "::::::::::::: %s\n", tagXMLEle(root)); - prXMLEle(stdout, root, 0); - - l = sprlXMLEle(root, 0); - str = malloc(l + 1); - fprintf(stderr, "::::::::::::: %s : %d : %d", tagXMLEle(root), l, sprXMLEle(str, root, 0)); - fprintf(stderr, ": %d\n", printf("%s", str)); - - delXMLEle(root); - } - else if (ynot[0]) - { - fprintf(stderr, "Error: %s\n", ynot); - } - - delLilXML(lp); - - return (0); -} -#endif - -#if defined(_MSC_VER) -#undef snprintf -#pragma warning(pop) -#endif diff --git a/src/core/base/lilxml.hpp b/src/core/base/lilxml.hpp deleted file mode 100644 index 664f848c..00000000 --- a/src/core/base/lilxml.hpp +++ /dev/null @@ -1,342 +0,0 @@ -#if 0 -liblilxml -Copyright (C) 2003 Elwood C. Downey -2022 Ludovic Pollet - -This library is free software; -you can redistribute it and / or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; -either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; -without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; -if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA - -#endif - -/** \file lilxml.h - \brief A little DOM-style library to handle parsing and processing an XML file. - - It only handles elements, attributes and pcdata content. and are silently ignored. pcdata is collected into one string, sans leading whitespace first line. \n - - The following is an example of a cannonical usage for the lilxml library. Initialize a lil xml context and read an XML file in a root element. - - \code - - #include - - LilXML *lp = newLilXML(); - char errmsg[1024]; - XMLEle *root, *ep; - int c; - - while ((c = fgetc(stdin)) != EOF) { - root = readXMLEle (lp, c, errmsg); - if (root) - break; - if (errmsg[0]) - error ("Error: %s\n", errmsg); - } - - // print the tag and pcdata content of each child element within the root - - for (ep = nextXMLEle (root, 1); ep != NULL; ep = nextXMLEle (root, 0)) - printf ("%s: %s\n", tagXMLEle(ep), pcdataXMLEle(ep)); - - - // finished with root element and with lil xml context - - delXMLEle (root); - delLilXML (lp); - - \endcode - - */ - -#pragma once - -#include - -#ifdef __cplusplus -extern "C" -{ -#endif - - /* opaque handle types */ - typedef struct xml_att_ XMLAtt; - typedef struct xml_ele_ XMLEle; - typedef struct LilXML_ LilXML; - - /** - * \defgroup lilxmlFunctions XML Functions: Functions to parse, process, and search XML. - */ - /*@{*/ - - /* creation and destruction functions */ - - /** \brief Create a new lilxml parser. - \return a pointer to the lilxml parser on success. NULL on failure. - */ - extern LilXML *newLilXML(); - - /** \brief Delete a lilxml parser. - \param lp a pointer to a lilxml parser to be deleted. - */ - extern void delLilXML(LilXML *lp); - - /** - * @brief delXMLEle Delete XML element. - * @param e Pointer to XML element to delete. If nullptr, no action is taken. - */ - extern void delXMLEle(XMLEle *e); - - /** \brief Process an XML chunk. - \param lp a pointer to a lilxml parser. - \param buf buffer to process. - \param size size of buf - \param errmsg a buffer to store error messages if an error in parsing is encountered. - \return return a pointer to a NULL terminated array of parsed XML elements. An array of size 1 with on a NULL element means there is nothing to parse or a parsing is still in progress. A NULL pointer may be returned if a parsing error occurs. Check errmsg for errors if NULL is returned. - */ - extern XMLEle **parseXMLChunk(LilXML *lp, char *buf, int size, char errmsg[]); - - /** \brief Process an XML one char at a time. - \param lp a pointer to a lilxml parser. - \param c one character to process. - \param errmsg a buffer to store error messages if an error in parsing is encounterd. - \return When the function parses a complete valid XML element, it will return a pointer to the XML element. A NULL is returned when parsing the element is still in progress, or if a parsing error occurs. Check errmsg for errors if NULL is returned. - */ - extern XMLEle *readXMLEle(LilXML *lp, int c, char errmsg[]); - - /* search functions */ - /** \brief Find an XML attribute within an XML element. - \param e a pointer to the XML element to search. - \param name the attribute name to search for. - \return A pointer to the XML attribute if found or NULL on failure. - */ - extern XMLAtt *findXMLAtt(XMLEle *e, const char *name); - - /** \brief Find an XML element within an XML element. - \param e a pointer to the XML element to search. - \param tag the element tag to search for. - \return A pointer to the XML element if found or NULL on failure. - */ - extern XMLEle *findXMLEle(XMLEle *e, const char *tag); - - /* iteration functions */ - /** \brief Iterate an XML element for a list of nesetd XML elements. - \param ep a pointer to the XML element to iterate. - \param first the index of the starting XML element. Pass 1 to start iteration from the beginning of the XML element. Pass 0 to get the next element thereater. - \return On success, a pointer to the next XML element is returned. NULL when there are no more elements. - */ - extern XMLEle *nextXMLEle(XMLEle *ep, int first); - - /** \brief Iterate an XML element for a list of XML attributes. - \param ep a pointer to the XML element to iterate. - \param first the index of the starting XML attribute. Pass 1 to start iteration from the beginning of the XML element. Pass 0 to get the next attribute thereater. - \return On success, a pointer to the next XML attribute is returned. NULL when there are no more attributes. - */ - extern XMLAtt *nextXMLAtt(XMLEle *ep, int first); - - /* tree functions */ - /** \brief Return the parent of an XML element. - \return a pointer to the XML element parent. - */ - extern XMLEle *parentXMLEle(XMLEle *ep); - - /** \brief Return the parent of an XML attribute. - \return a pointer to the XML element parent. - */ - extern XMLEle *parentXMLAtt(XMLAtt *ap); - - /* access functions */ - /** \brief Return the tag of an XML element. - \param ep a pointer to an XML element. - \return the tag string. - */ - extern char *tagXMLEle(XMLEle *ep); - - /** \brief Return the pcdata of an XML element. - \param ep a pointer to an XML element. - \return the pcdata string on success. - */ - extern char *pcdataXMLEle(XMLEle *ep); - - /** \brief Return the name of an XML attribute. - \param ap a pointer to an XML attribute. - \return the name string of the attribute. - */ - extern char *nameXMLAtt(XMLAtt *ap); - - /** \brief Return the value of an XML attribute. - \param ap a pointer to an XML attribute. - \return the value string of the attribute. - */ - extern char *valuXMLAtt(XMLAtt *ap); - - /** \brief Return the number of characters in pcdata in an XML element. - \param ep a pointer to an XML element. - \return the length of the pcdata string. - */ - extern int pcdatalenXMLEle(XMLEle *ep); - - /** \brief Return the number of nested XML elements in a parent XML element. - \param ep a pointer to an XML element. - \return the number of nested XML elements. - */ - extern int nXMLEle(XMLEle *ep); - - /** \brief Return the number of XML attributes in a parent XML element. - \param ep a pointer to an XML element. - \return the number of XML attributes within the XML element. - */ - extern int nXMLAtt(XMLEle *ep); - - /* editing functions */ - /** \brief add an element with the given tag to the given element. parent can be NULL to make a new root. - \return if parent is NULL, a new root is returned, otherwise, parent is returned. - */ - extern XMLEle *addXMLEle(XMLEle *parent, const char *tag); - - /** \brief Update the tag of an element - \param ep pointer to an XML element - \param tag the new tag value to set - */ - extern XMLEle *setXMLEleTag(XMLEle *ep, const char *tag); - - /** \brief set the pcdata of the given element - \param ep pointer to an XML element. - \param pcdata pcdata to set. - */ - extern void editXMLEle(XMLEle *ep, const char *pcdata); - - /** \brief Add an XML attribute to an existing XML element. - \param ep pointer to an XML element - \param name the name of the XML attribute to add. - \param value the value of the XML attribute to add. - */ - extern XMLAtt *addXMLAtt(XMLEle *ep, const char *name, const char *value); - - /** \brief Remove an XML attribute from an XML element. - \param ep pointer to an XML element. - \param name the name of the XML attribute to remove - */ - extern void rmXMLAtt(XMLEle *ep, const char *name); - - /** \brief change the value of an attribute to str. - * \param ap pointer to XML attribute - * \param str new attribute value - */ - extern void editXMLAtt(XMLAtt *ap, const char *str); - - /** \brief return a string with all xml-sensitive characters within the passed string replaced with their entity sequence equivalents. - * N.B. caller must use the returned string before calling us again. - * Warning: This will sometime fail in impredictible way when used from multiple thread contexts - */ - extern char *entityXML(char *str); - - /* convenience functions */ - /** \brief Find an XML element's attribute value. - \param ep a pointer to an XML element. - \param name the name of the XML attribute to retrieve its value. - \return the value string of an XML element on success. NULL on failure. - */ - extern const char *findXMLAttValu(XMLEle *ep, const char *name); - - /** \brief return a surface copy of a node. - Don't copy childs or cdata. - \return a new independant node - */ - extern XMLEle *shallowCloneXMLEle(XMLEle *ele); - - /** \brief clone (deep) a xmlEle. - Optional replacement function can be passed, to replace a whole subtree instead of copying - \param ele the original tree - \param replace function which can provide replacement. Return 1 & set replace for replacement. Optional - \param self additional value passed to replace function - \return a new independant node - */ - extern XMLEle *cloneXMLEle(XMLEle *ep, int (*replace)(void *self, XMLEle *source, XMLEle **replace), void *self); - - /** \brief Handy wrapper to read one xml file. - \param fp pointer to FILE to read. - \param lp pointer to lilxml parser. - \param errmsg a buffer to store error messages on failure. - \return root element else NULL with report in errmsg[]. - */ - extern XMLEle *readXMLFile(FILE *fp, LilXML *lp, char errmsg[]); - - /** \brief Print an XML element. - \param fp a pointer to FILE where the print output is directed. - \param e the XML element to print. - \param level the printing level, set to 0 to print the whole element. - */ - extern void prXMLEle(FILE *fp, XMLEle *e, int level); - - /** \brief sample print ep to string s. - * N.B. s must be at least as large as that reported by sprlXMLEle()+1. - * N.B. set level = 0 on first call. - * \return return length of resulting string (sans trailing @\0@) - */ - extern size_t sprXMLEle(char *s, XMLEle *ep, int level); - - /** \brief return number of bytes in a string guaranteed able to hold result of sprXLMEle(ep) (sans trailing @\0@). - * N.B. set level = 0 on first call. - */ - extern size_t sprlXMLEle(XMLEle *ep, int level); - - /** \brief return exact position of cdata of child in printed representation of root - * N.B. set level = 0 on first call. - */ - extern size_t sprXMLCDataOffset(XMLEle *root, XMLEle *child, int level); - - /* install alternatives to malloc/realloc/free */ - extern void indi_xmlMalloc(void *(*newmalloc)(size_t size), void *(*newrealloc)(void *ptr, size_t size), - void (*newfree)(void *ptr)); - - /*@}*/ - -#ifdef __cplusplus -} -#endif - -/* examples. - - initialize a lil xml context and read an XML file in a root element - - LilXML *lp = newLilXML(); - char errmsg[1024]; - XMLEle *root, *ep; - int c; - - while ((c = fgetc(stdin)) != EOF) { - root = readXMLEle (lp, c, errmsg); - if (root) - break; - if (errmsg[0]) - error ("Error: %s\n", errmsg); - } - - print the tag and pcdata content of each child element within the root - - for (ep = nextXMLEle (root, 1); ep != NULL; ep = nextXMLEle (root, 0)) - printf ("%s: %s\n", tagXMLEle(ep), pcdataXMLEle(ep)); - - - finished with root element and with lil xml context - - delXMLEle (root); - delLilXML (lp); - */ - -/* For RCS Only -- Do Not Edit - * @(#) $RCSfile$ $Date: 2007-09-17 16:34:48 +0300 (Mon, 17 Sep 2007) $ $Revision: 713418 $ $Name: $ - */ diff --git a/src/core/base/parentdevice.cpp b/src/core/base/parentdevice.cpp deleted file mode 100644 index ca871395..00000000 --- a/src/core/base/parentdevice.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "parentdevice.h" -#include "parentdevice_p.h" - -#include "basedevice.h" -#include "basedevice_p.h" - -#include - -namespace HYDROGEN -{ - - static std::shared_ptr create(bool valid) - { - class InvalidParentDevicePrivate : public ParentDevicePrivate - { - public: - InvalidParentDevicePrivate() - { - this->valid = false; - } - }; - - if (valid == false) - { - static InvalidParentDevicePrivate invalidDevice; - return make_shared_weak(&invalidDevice); - } - else - { - std::shared_ptr validDevice(new ParentDevicePrivate); - return validDevice; - } - } - - ParentDevice::ParentDevice(ParentDevice::Type type) - : BaseDevice(std::shared_ptr(create(type == Valid))) - { - D_PTR(ParentDevice); - ++d->ref; - } - - ParentDevice::ParentDevice(const std::shared_ptr &dd) - : BaseDevice(std::shared_ptr(dd)) - { - D_PTR(ParentDevice); - ++d->ref; - } - - ParentDevice::~ParentDevice() - { - D_PTR(ParentDevice); - if (--d->ref == 0) - { - // prevent circular reference - d->pAll.clear(); - } - } - -} diff --git a/src/core/base/parentdevice.h b/src/core/base/parentdevice.h deleted file mode 100644 index 4d84691e..00000000 --- a/src/core/base/parentdevice.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "basedevice.h" - -/** - * @class HYDROGEN::ParentDevice - * @brief The class is used to create device instances. - * Class copying is not allowed. When an object is destroyed, - * the property list (HYDROGEN::Property) is cleared to prevent a circular reference along with the properties. - * The base HYDROGEN::BaseDevice class and its HYDROGEN::Properties exist as long as they are used by other objects. - */ - -namespace HYDROGEN -{ - - class ParentDevicePrivate; - class ParentDevice : public BaseDevice - { - DECLARE_PRIVATE(ParentDevice) - - ParentDevice(const ParentDevice &) = delete; - ParentDevice &operator=(const ParentDevice &) = delete; - - public: - enum Type - { - Valid, - Invalid - }; - - public: - explicit ParentDevice(Type type); - ~ParentDevice(); - - public: - ParentDevice(ParentDevice &&other) = default; - ParentDevice &operator=(ParentDevice &&other) = default; - - protected: - ParentDevice(const std::shared_ptr &dd); - }; - -} diff --git a/src/core/base/parentdevice_p.h b/src/core/base/parentdevice_p.h deleted file mode 100644 index aac215fe..00000000 --- a/src/core/base/parentdevice_p.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "basedevice_p.h" -#include - -namespace HYDROGEN -{ - - class ParentDevicePrivate : public BaseDevicePrivate - { - public: - std::atomic_int ref{0}; - }; - -} diff --git a/src/core/base/sharedblob.cpp b/src/core/base/sharedblob.cpp deleted file mode 100644 index 74d24c16..00000000 --- a/src/core/base/sharedblob.cpp +++ /dev/null @@ -1,548 +0,0 @@ -/** HYDROGEN - * Copyright (C) 2022 by Ludovic Pollet - * - * This library is free software; - * you can redistribute it and / or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; - * without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; - * if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA - */ - -#include "sharedblob.hpp" - -#ifdef __linux__ -#include -#endif - -#include -#include -#include -#include -#include -#ifdef _WIN32 -#include -#else -#include -#endif -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY -#include "shm_open_anon.hpp" -#endif - -// A shared buffer will be allocated by chunk of at least 1M (must be ^ 2) -#define BLOB_SIZE_UNIT 0x100000 - -static pthread_mutex_t shared_buffer_mutex = PTHREAD_MUTEX_INITIALIZER; - -typedef struct shared_buffer -{ -#ifdef _WIN32 - HANDLE hMapObject; -#endif - void *mapstart; - size_t size; - size_t allocated; - int fd; - int sealed; - struct shared_buffer *prev, *next; -} shared_buffer; - -/* Return the buffer size required for storage (rounded to next BLOB_SIZE_UNIT) */ -static size_t allocation(size_t storage) -{ - if (storage == 0) - { - return BLOB_SIZE_UNIT; - } - return (storage + BLOB_SIZE_UNIT - 1) & ~(BLOB_SIZE_UNIT - 1); -} - -static void sharedBufferAdd(shared_buffer *sb); -static shared_buffer *sharedBufferRemove(void *mapstart); -static shared_buffer *sharedBufferFind(void *mapstart); - -void *IDSharedBlobAlloc(size_t size) -{ -#ifdef ENABLE_HYDROGEN_SHARED_MEMORY - shared_buffer *sb = (shared_buffer *)malloc(sizeof(shared_buffer)); - if (sb == NULL) - { - // 错误处理逻辑 - goto ERROR_LABEL; - } - - sb->size = size; - sb->allocated = allocation(size); - sb->sealed = 0; - sb->fd = -1; - -#ifdef _WIN32 - SECURITY_ATTRIBUTES sa; - sa.nLength = sizeof(SECURITY_ATTRIBUTES); - sa.lpSecurityDescriptor = NULL; - sa.bInheritHandle = TRUE; - - sb->hMapObject = CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, sb->allocated, NULL); - if (sb->hMapObject == NULL) - { - // 错误处理逻辑 - goto ERROR_LABEL; - } - - sb->mapstart = MapViewOfFile(sb->hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, sb->allocated); - if (sb->mapstart == NULL) - { - // 错误处理逻辑 - goto ERROR_LABEL; - } -#else - sb->fd = shm_open("/mysharedmem", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); - if (sb->fd == -1) - { - // 错误处理逻辑 - goto ERROR_LABEL; - } - - int ret = ftruncate(sb->fd, sb->allocated); - if (ret == -1) - { - // 错误处理逻辑 - goto ERROR_LABEL; - } - - sb->mapstart = mmap(NULL, sb->allocated, PROT_READ | PROT_WRITE, MAP_SHARED, sb->fd, 0); - if (sb->mapstart == MAP_FAILED) - { - // 错误处理逻辑 - goto ERROR_LABEL; - } -#endif - - sharedBufferAdd(sb); - - // 返回正常的结果 - return sb->mapstart; - -ERROR_LABEL: - if (sb) - { -#ifdef _WIN32 - DWORD e = GetLastError(); - if (sb->mapstart) - UnmapViewOfFile(sb->mapstart); - if (sb->hMapObject) - CloseHandle(sb->hMapObject); -#else - int e = errno; - if (sb->mapstart) - munmap(sb->mapstart, sb->allocated); - if (sb->fd != -1) - close(sb->fd); -#endif - free(sb); -#ifdef _WIN32 - SetLastError(e); -#else - errno = e; -#endif - } - - // 返回错误结果 - return NULL; -#else - return malloc(size); -#endif -} - -#ifdef _WIN32 -void *IDSharedBlobAttach(int fd, size_t size) -{ - shared_buffer *sb = (shared_buffer *)malloc(sizeof(shared_buffer)); - if (sb == NULL) - goto ERROR_LABEL; - sb->fd = fd; - sb->size = size; - sb->allocated = size; - sb->sealed = 1; - - sb->mapstart = MapViewOfFile((HANDLE)_get_osfhandle(fd), FILE_MAP_READ, 0, 0, size); - if (sb->mapstart == NULL) - goto ERROR_LABEL; - - sharedBufferAdd(sb); - - return sb->mapstart; - -ERROR_LABEL: - if (sb) - { - DWORD e = GetLastError(); - free(sb); - SetLastError(e); - } - return NULL; -} -#else -void *IDSharedBlobAttach(int fd, size_t size) -{ - shared_buffer *sb = (shared_buffer *)malloc(sizeof(shared_buffer)); - if (sb == NULL) - goto ERROR_LABEL; - sb->fd = fd; - sb->size = size; - sb->allocated = size; - sb->sealed = 1; - - sb->mapstart = mmap(NULL, sb->allocated, PROT_READ, MAP_SHARED, sb->fd, 0); - if (sb->mapstart == MAP_FAILED) - goto ERROR_LABEL; - - sharedBufferAdd(sb); - - return sb->mapstart; - -ERROR_LABEL: - if (sb) - { - int e = errno; - free(sb); - errno = e; - } - return NULL; -} -#endif - -#ifdef _WIN32 -void IDSharedBlobFree(void *ptr) -{ - shared_buffer *sb = sharedBufferRemove(ptr); - if (sb == NULL) - { - // 不是连接到共享内存的内存块 - free(ptr); - return; - } - - if (!UnmapViewOfFile(sb->mapstart)) - { - perror("shared buffer UnmapViewOfFile"); - _exit(1); - } - if (!CloseHandle(sb->hMapObject)) - { - perror("shared buffer CloseHandle"); - } - free(sb); -} -#else -void IDSharedBlobFree(void *ptr) -{ - shared_buffer *sb = sharedBufferRemove(ptr); - if (sb == NULL) - { - // 不是连接到共享内存的内存块 - free(ptr); - return; - } - - if (munmap(sb->mapstart, sb->allocated) == -1) - { - perror("shared buffer munmap"); - _exit(1); - } - if (close(sb->fd) == -1) - { - perror("shared buffer close"); - } - free(sb); -} -#endif - -#ifdef _WIN32 -void IDSharedBlobDettach(void *ptr) -{ - shared_buffer *sb = sharedBufferRemove(ptr); - if (sb == NULL) - { - free(ptr); - return; - } - if (UnmapViewOfFile(sb->mapstart) == 0) - { - perror("shared buffer UnmapViewOfFile"); - exit(1); - } - free(sb); -} -#else -void IDSharedBlobDettach(void *ptr) -{ - shared_buffer *sb = sharedBufferRemove(ptr); - if (sb == NULL) - { - free(ptr); - return; - } - if (munmap(sb->mapstart, sb->allocated) == -1) - { - perror("shared buffer munmap"); - exit(1); - } - free(sb); -} -#endif - -#ifdef _WIN32 -void *IDSharedBlobRealloc(void *ptr, size_t size) -{ - if (ptr == NULL) - { - return IDSharedBlobAlloc(size); - } - - shared_buffer *sb; - sb = sharedBufferFind(ptr); - - if (sb == NULL) - { - return realloc(ptr, size); - } - - if (sb->sealed) - { - IDSharedBlobFree(ptr); - errno = EROFS; - return NULL; - } - - if (sb->size >= size) - { - // 缩小尺寸未实现 - sb->size = size; - return ptr; - } - - size_t reallocated = allocation(size); - if (reallocated == sb->allocated) - { - sb->size = size; - return ptr; - } - - // 在Windows平台上使用VirtualAlloc重新分配内存,需要先取消当前内存映射,再重新映射 - if (UnmapViewOfFile(sb->mapstart) == 0) - { - perror("shared buffer UnmapViewOfFile"); - _exit(1); - } - void *remapped = VirtualAlloc(NULL, reallocated, MEM_COMMIT, PAGE_READWRITE); - if (remapped == NULL) - { - perror("shared buffer VirtualAlloc"); - _exit(1); - } - void *ret = MapViewOfFileEx(sb->hMapObject, FILE_MAP_WRITE, 0, 0, 0, remapped); - if (ret == NULL) - { - perror("shared buffer MapViewOfFileEx"); - _exit(1); - } - - sb->size = size; - sb->allocated = reallocated; - sb->mapstart = remapped; - - return remapped; -} - -static void seal(shared_buffer *sb) -{ - void *ret = MapViewOfFileEx(sb->hMapObject, FILE_MAP_READ, 0, 0, 0, sb->mapstart); - if (ret == NULL) - { - perror("remap readonly failed"); - _exit(1); - } - sb->sealed = 1; -} -#else -void *IDSharedBlobRealloc(void *ptr, size_t size) -{ - if (ptr == NULL) - { - return IDSharedBlobAlloc(size); - } - - shared_buffer *sb; - sb = sharedBufferFind(ptr); - - if (sb == NULL) - { - return realloc(ptr, size); - } - - if (sb->sealed) - { - IDSharedBlobFree(ptr); - errno = EROFS; - return NULL; - } - - if (sb->size >= size) - { - // 缩小尺寸未实现 - sb->size = size; - return ptr; - } - - size_t reallocated = allocation(size); - if (reallocated == sb->allocated) - { - sb->size = size; - return ptr; - } - -#ifdef HAVE_MREMAP - void *remapped = mremap(sb->mapstart, sb->allocated, reallocated, MREMAP_MAYMOVE); - if (remapped == MAP_FAILED) - { - perror("shared buffer mremap"); - _exit(1); - } -#else - // 兼容MACOS平台的路径 - if (munmap(sb->mapstart, sb->allocated) == -1) - { - perror("shared buffer munmap"); - _exit(1); - } - void *remapped = mmap(0, reallocated, PROT_READ | PROT_WRITE, MAP_SHARED, sb->fd, 0); - if (remapped == MAP_FAILED) - { - perror("shared buffer mmap"); - _exit(1); - } -#endif - - sb->size = size; - sb->allocated = reallocated; - sb->mapstart = remapped; - - return remapped; -} - -static void seal(shared_buffer *sb) -{ - void *ret = mmap(sb->mapstart, sb->allocated, PROT_READ, MAP_SHARED | MAP_FIXED, sb->fd, 0); - if (ret == MAP_FAILED) - { - perror("remap readonly failed"); - _exit(1); - } - sb->sealed = 1; -} -#endif - -int IDSharedBlobGetFd(void *ptr) -{ - shared_buffer *sb; - sb = sharedBufferFind(ptr); - if (sb == NULL) - { - errno = EINVAL; - return -1; - } - - // Make sure a shared blob is not modified after sharing - seal(sb); - - return sb->fd; -} - -void IDSharedBlobSeal(void *ptr) -{ - shared_buffer *sb; - sb = sharedBufferFind(ptr); - if (sb->sealed) - return; - seal(sb); -} - -static shared_buffer *first = NULL, *last = NULL; - -static void sharedBufferAdd(shared_buffer *sb) -{ - pthread_mutex_lock(&shared_buffer_mutex); - // Chained insert at start - sb->prev = NULL; - sb->next = first; - if (first) - { - first->prev = sb; - } - else - { - last = sb; - } - first = sb; - pthread_mutex_unlock(&shared_buffer_mutex); -} -static shared_buffer *sharedBufferFindUnlocked(void *mapstart) -{ - shared_buffer *sb = first; - while (sb) - { - if (sb->mapstart == mapstart) - { - return sb; - } - sb = sb->next; - } - return NULL; -} - -static shared_buffer *sharedBufferRemove(void *mapstart) -{ - pthread_mutex_lock(&shared_buffer_mutex); - shared_buffer *sb = sharedBufferFindUnlocked(mapstart); - if (sb != NULL) - { - if (sb->prev) - { - sb->prev->next = sb->next; - } - else - { - first = sb->next; - } - if (sb->next) - { - sb->next->prev = sb->prev; - } - else - { - last = sb->prev; - } - } - pthread_mutex_unlock(&shared_buffer_mutex); - return sb; -} - -static shared_buffer *sharedBufferFind(void *mapstart) -{ - pthread_mutex_lock(&shared_buffer_mutex); - shared_buffer *sb = sharedBufferFindUnlocked(mapstart); - pthread_mutex_unlock(&shared_buffer_mutex); - return sb; -} diff --git a/src/core/base/sharedblob.hpp b/src/core/base/sharedblob.hpp deleted file mode 100644 index 4adad5a0..00000000 --- a/src/core/base/sharedblob.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#define HYDROGEN_SHARED_BLOB_SUPPORT - -#include - -#pragma once - -/** \brief Allocate a buffer suitable for fast exchange over local links. Warning : the buffer will be sealed (readonly) once exchanged. - * \param size_t size of the memory area to allocate - */ -void *IDSharedBlobAlloc(size_t size); - -/** - * Attach to a received shared buffer by ID - * The returned buffer cannot be realloced or sealed. - * \return null on error + errno (invalid fd / system resources) - */ -void *IDSharedBlobAttach(int fd, size_t size); - -/** \brief Free a buffer allocated using IDSharedBlobAlloc. Fall back to free for buffer that are not shared blob - * Must be used for IBLOB.data - */ -void IDSharedBlobFree(void *ptr); - -/** \brief Dettach a blob, but don't close its FD - */ -void IDSharedBlobDettach(void *ptr); - -/** \brief Adjust the size of a buffer obtained using IDSharedBlobAlloc. - * \param size_t size of the memory area to allocate - */ -void *IDSharedBlobRealloc(void *ptr, size_t size); - -/** \brief Return the filedescriptor backing up the given shared buffer. - * \return the filedescriptor or -1 if not a shared buffer pointer - */ -int IDSharedBlobGetFd(void *ptr); - -/** \brief Seal (make readonly) a buffer allocated using IDSharedBlobAlloc. This is automatic when IDNewBlob - * \param size_t size of the memory area to allocate - */ -void IDSharedBlobSeal(void *ptr); - -void *IDSharedBlobAlloc(size_t size); diff --git a/src/core/base/sharedblob_parse.cpp b/src/core/base/sharedblob_parse.cpp deleted file mode 100644 index 8949b89f..00000000 --- a/src/core/base/sharedblob_parse.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/******************************************************************************* - Copyright(c) 2022 Ludovic Pollet. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#include "sharedblob_parse.hpp" - -#define HYDROGEN_SHARED_BLOB_SUPPORT -#include "sharedblob.hpp" - -#include -#include -#include -#include - -#include - -namespace HYDROGEN -{ - - static std::mutex attachedBlobMutex; - static std::map receivedFds; - static uint64_t idGenerator = rand(); - - std::string allocateBlobUid(int fd) - { - std::lock_guard lock(attachedBlobMutex); - - std::stringstream ss; - ss << idGenerator++; - - std::string id = ss.str(); - - receivedFds[id] = fd; - return id; - } - - void *attachBlobByUid(const std::string &identifier, size_t size) - { - int fd; - { - std::lock_guard lock(attachedBlobMutex); - auto where = receivedFds.find(identifier); - if (where == receivedFds.end()) - { - return nullptr; - } - fd = where->second; - receivedFds.erase(where); - } - - return IDSharedBlobAttach(fd, size); - } - - void releaseBlobUids(const std::vector &blobs) - { - std::vector toDestroy; - { - std::lock_guard lock(attachedBlobMutex); - for (auto id : blobs) - { - auto idPos = receivedFds.find(id); - if (idPos != receivedFds.end()) - { - toDestroy.push_back(idPos->second); - receivedFds.erase(idPos); - } - } - } - - for (auto fd : toDestroy) - { - ::close(fd); - } - } - -} diff --git a/src/core/base/sharedblob_parse.hpp b/src/core/base/sharedblob_parse.hpp deleted file mode 100644 index 367b7850..00000000 --- a/src/core/base/sharedblob_parse.hpp +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************* - Copyright(c) 2022 Ludovic Pollet. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#pragma once - -#include -#include - -namespace HYDROGEN -{ - - // Allocate a new uuid for this blob content - std::string allocateBlobUid(int fd); - - // Release the blob for which uid has not been attached - void releaseBlobUids(const std::vector &uids); - - // Attach the given blob buffer and release it's uid - void *attachBlobByUid(const std::string &uid, size_t size); - -} diff --git a/src/core/base/shm_open_anon.cpp b/src/core/base/shm_open_anon.cpp deleted file mode 100644 index 6204ff8b..00000000 --- a/src/core/base/shm_open_anon.cpp +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2019 Lassi Kortela -// SPDX-License-Identifier: ISC - -#ifdef __linux__ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#endif - -#include -#ifndef _WIN32 -#include -#endif -#include -#include -#include -#include -#include - -#undef IMPL_MEMFD -#undef IMPL_POSIX -#undef IMPL_SHM_ANON -#undef IMPL_SHM_MKSTEMP -#undef IMPL_UNLINK_OR_CLOSE - -#ifdef __linux__ -#ifdef __NR_memfd_create -#define IMPL_MEMFD -#endif -#endif - -#ifdef __FreeBSD__ -#define IMPL_SHM_ANON -#endif - -#ifdef __HAIKU__ -#define IMPL_POSIX -#endif - -#ifdef __NetBSD__ -#define IMPL_POSIX -#endif - -#ifdef __APPLE__ -#ifdef __MACH__ -#define IMPL_POSIX -#endif -#endif - -#ifdef __sun -#define IMPL_POSIX -#endif - -#ifdef __DragonFly__ -#define IMPL_POSIX -#endif - -#ifdef __GNU__ -#define IMPL_POSIX -#endif - -#ifdef __OpenBSD__ -#define IMPL_SHM_MKSTEMP -#endif - -#ifdef __CYGWIN__ -#define IMPL_POSIX -#endif - -#ifdef IMPL_POSIX -#define IMPL_UNLINK_OR_CLOSE -#endif - -#ifdef IMPL_SHM_MKSTEMP -#define IMPL_UNLINK_OR_CLOSE -#endif - -#ifdef IMPL_UNLINK_OR_CLOSE -static int -shm_unlink_or_close(const char *name, int fd) -{ - int save; - - if (shm_unlink(name) == -1) - { - save = errno; - close(fd); - errno = save; - return -1; - } - return fd; -} -#endif - -#ifdef IMPL_POSIX -int shm_open_anon(void) -{ - char name[16] = "/shm-"; - struct timespec tv; - unsigned long r; - char *const limit = name + sizeof(name) - 1; - char *start; - char *fill; - int fd, tries; - - *limit = 0; - start = name + strlen(name); - for (tries = 0; tries < 4; tries++) - { - clock_gettime(CLOCK_REALTIME, &tv); - r = (unsigned long)tv.tv_sec + (unsigned long)tv.tv_nsec; - for (fill = start; fill < limit; r /= 8) - *fill++ = '0' + (r % 8); - fd = shm_open( - name, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600); - if (fd != -1) - return shm_unlink_or_close(name, fd); - if (errno != EEXIST) - break; - } - return -1; -} -#endif - -#ifdef IMPL_SHM_MKSTEMP -int shm_open_anon(void) -{ - char name[16] = "/shm-XXXXXXXXXX"; - int fd; - - if ((fd = shm_mkstemp(name)) == -1) - return -1; - return shm_unlink_or_close(name, fd); -} -#endif - -#ifdef IMPL_SHM_ANON -int shm_open_anon(void) -{ - return shm_open(SHM_ANON, O_RDWR, 0); -} -#endif - -#ifdef IMPL_MEMFD -int shm_open_anon(void) -{ - return syscall( - __NR_memfd_create, "shm_anon", (unsigned int)(MFD_CLOEXEC)); -} -#endif diff --git a/src/core/base/shm_open_anon.hpp b/src/core/base/shm_open_anon.hpp deleted file mode 100644 index 09ba2302..00000000 --- a/src/core/base/shm_open_anon.hpp +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2019 Lassi Kortela -// SPDX-License-Identifier: ISC - -#pragma once - -#ifdef __cplusplus -extern "C" -{ -#endif - - int shm_open_anon(void); - -#ifdef __cplusplus -} -#endif diff --git a/src/core/base/userio.cpp b/src/core/base/userio.cpp deleted file mode 100644 index 4ff9ee97..00000000 --- a/src/core/base/userio.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "userio.hpp" - -#include -#include - -static ssize_t s_file_write(void *user, const void *ptr, size_t count) -{ - return std::fwrite(ptr, 1, count, reinterpret_cast(user)); -} - -static int s_file_printf(void *user, const char *format, va_list arg) -{ - return std::vfprintf(reinterpret_cast(user), format, arg); -} - -static const struct userio s_userio_file = { - .write = s_file_write, - .vprintf = s_file_printf, - .joinbuff = nullptr, -}; - -const struct userio *userio_file() -{ - return &s_userio_file; -} - -ssize_t userio_printf(const struct userio *io, void *user, const char *format, ...) -{ - int ret; - va_list ap; - va_start(ap, format); - ret = io->vprintf(user, format, ap); - va_end(ap); - return ret; -} - -ssize_t userio_vprintf(const struct userio *io, void *user, const char *format, va_list arg) -{ - return io->vprintf(user, format, arg); -} - -ssize_t userio_write(const struct userio *io, void *user, const void *ptr, size_t count) -{ - return io->write(user, ptr, count); -} - -ssize_t userio_prints(const struct userio *io, void *user, std::string_view str) -{ - return io->write(user, str.data(), str.size()); -} - -ssize_t userio_putc(const struct userio *io, void *user, int ch) -{ - char c = ch; - return io->write(user, &c, sizeof(c)); -} - -size_t userio_xml_escape(const struct userio *io, void *user, std::string_view src) -{ - size_t total = 0; - const char *ptr = src.data(); - const char *replacement; - - for (; ptr < src.data() + src.size(); ++ptr) - { - switch (*ptr) - { - case '&': - replacement = "&"; - break; - case '\'': - replacement = "'"; - break; - case '"': - replacement = """; - break; - case '<': - replacement = "<"; - break; - case '>': - replacement = ">"; - break; - default: - replacement = nullptr; - } - - if (replacement != nullptr) - { - total += userio_write(io, user, src.data(), ptr - src.data()); - src = {ptr + 1, src.size() - (ptr - src.data() + 1)}; - total += userio_write(io, user, replacement, std::strlen(replacement)); - } - } - total += userio_write(io, user, src.data(), ptr - src.data()); - return total; -} - -void userio_xmlv1(const userio *io, void *user) -{ - userio_prints(io, user, "\n"); -} - -ssize_t userio_json_write_string(const struct userio *io, void *user, std::string_view str) -{ - ssize_t ret; - ret = userio_putc(io, user, '\"'); - if (ret < 0) - return ret; - - for (const auto c : str) - { - switch (c) - { - case '\"': - ret = userio_prints(io, user, "\\\""); - break; - case '\\': - ret = userio_prints(io, user, "\\\\"); - break; - case '\b': - ret = userio_prints(io, user, "\\b"); - break; - case '\f': - ret = userio_prints(io, user, "\\f"); - break; - case '\n': - ret = userio_prints(io, user, "\\n"); - break; - case '\r': - ret = userio_prints(io, user, "\\r"); - break; - case '\t': - ret = userio_prints(io, user, "\\t"); - break; - default: - if (c < ' ') - { - ret = userio_printf(io, user, "\\u%04x", c); - } - else - { - ret = userio_putc(io, user, c); - } - break; - } - - if (ret < 0) - return ret; - } - - ret = userio_putc(io, user, '\"'); - return ret >= 0 ? ret + 2 : ret; -} - -ssize_t userio_json_write_number(const struct userio *io, void *user, double number) -{ - return userio_printf(io, user, "%g", number); -} - -ssize_t userio_json_write_boolean(const struct userio *io, void *user, bool value) -{ - if (value) - return userio_prints(io, user, "true"); - else - return userio_prints(io, user, "false"); -} - -ssize_t userio_json_write_null(const struct userio *io, void *user) -{ - return userio_prints(io, user, "null"); -} \ No newline at end of file diff --git a/src/core/base/userio.hpp b/src/core/base/userio.hpp deleted file mode 100644 index 2cd9346d..00000000 --- a/src/core/base/userio.hpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - 2022 by Ludovic Pollet - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" -{ -#endif - - typedef struct userio - { - ssize_t (*write)(void *user, const void *ptr, size_t count); - int (*vprintf)(void *user, const char *format, va_list arg); - - // join the given shared buffer as ancillary data. xml must be at least one char - optional - void (*joinbuff)(void *user, const char *xml, void *buffer, size_t bloblen); - } userio; - - const struct userio *userio_file(); - - ssize_t userio_printf(const struct userio *io, void *user, const char *format, ...); - ssize_t userio_vprintf(const struct userio *io, void *user, const char *format, va_list arg); - - ssize_t userio_write(const struct userio *io, void *user, const void *ptr, size_t count); - - ssize_t userio_putc(const struct userio *io, void *user, int ch); - - // extras - ssize_t userio_prints(const struct userio *io, void *user, std::string_view str); - size_t userio_xml_escape(const struct userio *io, void *user, std::string_view src); - void userio_xmlv1(const userio *io, void *user); - - // JSON support - ssize_t userio_json_write_string(const struct userio *io, void *user, std::string_view str); - ssize_t userio_json_write_number(const struct userio *io, void *user, double number); - ssize_t userio_json_write_boolean(const struct userio *io, void *user, bool value); - ssize_t userio_json_write_null(const struct userio *io, void *user); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/src/core/base/watchdeviceproperty.cpp b/src/core/base/watchdeviceproperty.cpp deleted file mode 100644 index e4e6a55b..00000000 --- a/src/core/base/watchdeviceproperty.cpp +++ /dev/null @@ -1,155 +0,0 @@ -/* - Copyright (C) 2022 Pawel Soja - Copyright (C) 2011 Jasem Mutlaq. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "watchdeviceproperty.h" -#include "basedevice.h" -#include "basedevice_p.h" - -namespace HYDROGEN -{ - - std::vector WatchDeviceProperty::getDevices() const - { - std::vector result; - for (const auto &it : data) - { - result.push_back(it.second.device); - } - return result; - } - - BaseDevice WatchDeviceProperty::getDeviceByName(const char *name) - { - auto it = data.find(name); - return it != data.end() ? it->second.device : BaseDevice(); - } - - WatchDeviceProperty::DeviceInfo &WatchDeviceProperty::ensureDeviceByName(const char *name, const std::function &constructor) - { - auto &it = data[name]; - if (!it.device.isValid()) - { - it.device = constructor(); - it.device.setDeviceName(name); - it.device.attach(); - it.emitWatchDevice(); - } - return it; - } - - bool WatchDeviceProperty::isEmpty() const - { - return data.empty(); - } - - bool WatchDeviceProperty::isDeviceWatched(const char *name) const - { - return watchedDevice.size() == 0 || watchedDevice.find(name) != watchedDevice.end(); - } - - void WatchDeviceProperty::unwatchDevices() - { - watchedDevice.clear(); - } - - void WatchDeviceProperty::watchDevice(const std::string &deviceName) - { - watchedDevice.insert(deviceName); - } - - void WatchDeviceProperty::watchDevice(const std::string &deviceName, const std::function &callback) - { - watchedDevice.insert(deviceName); - data[deviceName].newDeviceCallback = callback; - } - - void WatchDeviceProperty::watchProperty(const std::string &deviceName, const std::string &propertyName) - { - watchedDevice.insert(deviceName); - data[deviceName].properties.insert(propertyName); - } - - void WatchDeviceProperty::clear() - { - data.clear(); - } - - void WatchDeviceProperty::clearDevices() - { - for (auto &deviceInfo : data) - { - deviceInfo.second.device = ParentDevice(ParentDevice::Invalid); - } - } - - bool WatchDeviceProperty::deleteDevice(const BaseDevice &device) - { - for (auto it = data.begin(); it != data.end();) - { - if (it->second.device == device) - { - it = data.erase(it); - return true; - } - else - ++it; - } - return false; - } - - int WatchDeviceProperty::processXml(const HYDROGEN::LilXmlElement &root, char *errmsg, const std::function &constructor) - { - auto deviceName = root.getAttribute("device"); - if (!deviceName.isValid() || deviceName.toString() == "" || !isDeviceWatched(deviceName)) - { - return 0; - } - - // Get the device information, if not available, create it - auto &deviceInfo = ensureDeviceByName(deviceName, constructor); - - // If we are asked to watch for specific properties only, we ignore everything else - if (deviceInfo.properties.size() != 0) - { - const auto it = deviceInfo.properties.find(root.getAttribute("name").toString()); - if (it == deviceInfo.properties.end()) - return 0; - } - - static const std::set defVectors{ - "defTextVector", "defNumberVector", "defSwitchVector", - "defLightVector", "defBLOBVector"}; - - if (defVectors.find(root.tagName()) != defVectors.end()) - { - return deviceInfo.device.buildProp(root, errmsg); - } - - static const std::set setVectors{ - "setTextVector", "setNumberVector", "setSwitchVector", - "setLightVector", "setBLOBVector"}; - - if (setVectors.find(root.tagName()) != setVectors.end()) - { - return deviceInfo.device.setValue(root, errmsg); - } - - return HYDROGEN_DISPATCH_ERROR; - } - -} diff --git a/src/core/base/watchdeviceproperty.h b/src/core/base/watchdeviceproperty.h deleted file mode 100644 index a1925333..00000000 --- a/src/core/base/watchdeviceproperty.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - Copyright (C) 2022 Pawel Soja - Copyright (C) 2011 Jasem Mutlaq. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#pragma once - -#include "basedevice.h" -#include "parentdevice.h" -#include "hydrogenlilxml.hpp" - -#include -#include -#include -#include - -namespace HYDROGEN -{ - // Internal use only - Common implementatnion for client and driver side - class WatchDeviceProperty - { - public: - struct DeviceInfo - { - ParentDevice device{ParentDevice::Invalid}; - std::function newDeviceCallback; // call if device available - std::set properties; // watch only specific properties only - - void emitWatchDevice() - { - if (newDeviceCallback) - newDeviceCallback(device); - } - }; - - public: - std::vector getDevices() const; - BaseDevice getDeviceByName(const char *name); - DeviceInfo &ensureDeviceByName(const char *name, const std::function &constructor); - - public: - bool isEmpty() const; - - /** - * @brief checks if the device is being watched by something - * - * @param deviceName name of the searched device on the list - * @return true if the device is in the list or the list is empty - */ - bool isDeviceWatched(const char *deviceName) const; - - public: - void unwatchDevices(); - - void watchDevice(const std::string &deviceName); - void watchDevice(const std::string &deviceName, const std::function &callback); - - void watchProperty(const std::string &deviceName, const std::string &propertyName); - - void clear(); - void clearDevices(); - bool deleteDevice(const BaseDevice &device); - - public: - int processXml( - const HYDROGEN::LilXmlElement &root, char *errmsg, const std::function &constructor = [] - { return ParentDevice(ParentDevice::Valid); }); - - public: - std::map::iterator begin() - { - return data.begin(); - } - - std::map::iterator end() - { - return data.end(); - } - - protected: - std::set watchedDevice; - std::map data; - }; - -} diff --git a/src/core/locale/locale_compat.hpp b/src/core/locale/locale_compat.hpp deleted file mode 100644 index 11df95ca..00000000 --- a/src/core/locale/locale_compat.hpp +++ /dev/null @@ -1,151 +0,0 @@ -/* - * locale/locale_compat.hpppp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-4-9 - -Description: Locale Support (Copy from HYDROGEN) - -**************************************************/ - -#pragma once - -#include - -#ifdef __cplusplus -extern "C" -{ -#endif - - // C interface - // - // usage: - // - // locale_char_t *save = hydrogen_locale_C_numeric_push(); - // ... - // hydrogen_locale_C_numeric_pop(save); - // - -#if defined(_MSC_VER) - -#include -#include - - typedef wchar_t locale_char_t; -#define HYDROGEN_LOCALE(s) L"" #s - - __inline static locale_char_t *hydrogen_setlocale(int category, const locale_char_t *locale) - { - return _wcsdup(_wsetlocale(category, locale)); - } - - __inline static void hydrogen_restore_locale(int category, locale_char_t *prev) - { - _wsetlocale(category, prev); - free(prev); - } - -#define _HYDROGEN_C_INLINE __inline - -#else // _MSC_VER - -typedef char locale_char_t; -#define HYDROGEN_LOCALE(s) s - -inline static locale_char_t *hydrogen_setlocale(int category, const locale_char_t *locale) -{ - return setlocale(category, locale); -} - -inline static void hydrogen_restore_locale(int category, locale_char_t *prev) -{ - setlocale(category, prev); -} - -#define _HYDROGEN_C_INLINE inline - -#endif // _MSC_VER - - _HYDROGEN_C_INLINE static locale_char_t *hydrogen_locale_C_numeric_push() - { - return hydrogen_setlocale(LC_NUMERIC, HYDROGEN_LOCALE("C")); - } - - _HYDROGEN_C_INLINE static void hydrogen_locale_C_numeric_pop(locale_char_t *prev) - { - hydrogen_restore_locale(LC_NUMERIC, prev); - } - -#undef _HYDROGEN_C_INLINE - -#ifdef __cplusplus -} -#endif - -#ifdef __cplusplus - -// C++ interface -// -// usage: -// -// AutoCNumeric locale; // LC_NUMERIC locale set to "C" for object scope -// ... -// - -class AutoLocale -{ - int m_category; - locale_char_t *m_orig; - -public: - AutoLocale(int category, const locale_char_t *locale) - : m_category(category) - { - m_orig = hydrogen_setlocale(category, locale); - } - - // method Restore can be used to restore the original locale - // before the object goes out of scope - void Restore() - { - if (m_orig) - { - hydrogen_restore_locale(m_category, m_orig); - m_orig = nullptr; - } - } - - ~AutoLocale() - { - Restore(); - } -}; - -class AutoCNumeric : public AutoLocale -{ -public: - AutoCNumeric() : AutoLocale(LC_NUMERIC, HYDROGEN_LOCALE("C")) {} -}; - -#endif // __cplusplus diff --git a/src/core/property/hydrogenproperties.cpp b/src/core/property/hydrogenproperties.cpp deleted file mode 100644 index b1e3798b..00000000 --- a/src/core/property/hydrogenproperties.cpp +++ /dev/null @@ -1,221 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "hydrogenproperties.h" -#include "hydrogenproperties_p.h" - -namespace HYDROGEN -{ - - PropertiesPrivate::PropertiesPrivate() - { - } - - PropertiesPrivate::~PropertiesPrivate() - { - } - - Properties::Properties() - : d_ptr(new PropertiesPrivate) - { - } - - Properties::~Properties() - { - } - - Properties::Properties(std::shared_ptr dd) - : d_ptr(dd) - { - } - - void Properties::push_back(const HYDROGEN::Property &property) - { - D_PTR(Properties); - d->properties.push_back(property); - } - - void Properties::push_back(HYDROGEN::Property &&property) - { - D_PTR(Properties); - d->properties.push_back(std::move(property)); - } - - void Properties::clear() - { - D_PTR(Properties); - d->properties.clear(); - } - - Properties::size_type Properties::size() const - { - D_PTR(const Properties); - return d->properties.size(); - } - - Properties::reference Properties::at(size_type pos) - { - D_PTR(Properties); - return d->properties.at(pos); - } - - Properties::const_reference Properties::at(size_type pos) const - { - D_PTR(const Properties); - return d->properties.at(pos); - } - - Properties::reference Properties::operator[](Properties::size_type pos) - { - D_PTR(Properties); - return d->properties.at(pos); - } - - Properties::const_reference Properties::operator[](Properties::size_type pos) const - { - D_PTR(const Properties); - return d->properties.at(pos); - } - - Properties::reference Properties::front() - { - D_PTR(Properties); - return d->properties.front(); - } - - Properties::const_reference Properties::front() const - { - D_PTR(const Properties); - return d->properties.front(); - } - - Properties::reference Properties::back() - { - D_PTR(Properties); - return d->properties.back(); - } - - Properties::const_reference Properties::back() const - { - D_PTR(const Properties); - return d->properties.back(); - } - - Properties::iterator Properties::begin() - { - D_PTR(Properties); - return d->properties.begin(); - } - - Properties::iterator Properties::end() - { - D_PTR(Properties); - return d->properties.end(); - } - - Properties::const_iterator Properties::begin() const - { - D_PTR(const Properties); - return d->properties.begin(); - } - - Properties::const_iterator Properties::end() const - { - D_PTR(const Properties); - return d->properties.end(); - } - - Properties::iterator Properties::erase(iterator pos) - { - D_PTR(Properties); - return d->properties.erase(pos); - } - - Properties::iterator Properties::erase(const_iterator pos) - { - D_PTR(Properties); - return d->properties.erase(pos); - } - - Properties::iterator Properties::erase(iterator first, iterator last) - { - D_PTR(Properties); - return d->properties.erase(first, last); - } - - Properties::iterator Properties::erase(const_iterator first, const_iterator last) - { - D_PTR(Properties); - return d->properties.erase(first, last); - } - -#ifdef HYDROGEN_PROPERTIES_BACKWARD_COMPATIBILE - HYDROGEN::Properties Properties::operator*() - { - return *this; - } - - const HYDROGEN::Properties Properties::operator*() const - { - return *this; - } - - Properties *Properties::operator->() - { - return this; - } - - const Properties *Properties::operator->() const - { - return this; - } - - Properties::operator Properties *() - { - D_PTR(Properties); - return &d->self; - } - - Properties::operator const Properties *() const - { - D_PTR(const Properties); - return &d->self; - } - - Properties::operator std::vector *() - { - D_PTR(Properties); - d->propertiesBC.resize(0); - d->propertiesBC.reserve(d->properties.size()); - for (auto &it : d->properties) - d->propertiesBC.push_back(&it); - - return &d->propertiesBC; - } - - Properties::operator const std::vector *() const - { - D_PTR(const Properties); - d->propertiesBC.resize(0); - d->propertiesBC.reserve(d->properties.size()); - for (auto &it : d->properties) - d->propertiesBC.push_back(const_cast(&it)); - - return &d->propertiesBC; - } -#endif -} diff --git a/src/core/property/hydrogenproperties.h b/src/core/property/hydrogenproperties.h deleted file mode 100644 index 19c4c5ad..00000000 --- a/src/core/property/hydrogenproperties.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#pragma once - -#include "hydrogenproperty.h" -#include "util/macro.hpp" -#include -#include -#include - -#define HYDROGEN_PROPERTIES_BACKWARD_COMPATIBILE -namespace HYDROGEN -{ - - class PropertiesPrivate; - class Properties - { - DECLARE_PRIVATE(Properties) - - public: - using iterator = std::deque::iterator; - using const_iterator = std::deque::const_iterator; - using reference = std::deque::reference; - using const_reference = std::deque::const_reference; - using size_type = std::deque::size_type; - - public: - Properties(); - ~Properties(); - - public: - void push_back(const HYDROGEN::Property &property); - void push_back(HYDROGEN::Property &&property); - void clear(); - - public: - bool empty() const; - - public: - size_type size() const; - - public: - reference at(size_type pos); - const_reference at(size_type pos) const; - - reference operator[](size_type pos); - const_reference operator[](size_type pos) const; - - reference front(); - const_reference front() const; - - reference back(); - const_reference back() const; - - public: - iterator begin(); - iterator end(); - - const_iterator begin() const; - const_iterator end() const; - - public: - iterator erase(iterator pos); - iterator erase(const_iterator pos); - iterator erase(iterator first, iterator last); - iterator erase(const_iterator first, const_iterator last); - - template - iterator erase_if(Predicate predicate); - - public: -#ifdef HYDROGEN_PROPERTIES_BACKWARD_COMPATIBILE - HYDROGEN::Properties operator*(); - const HYDROGEN::Properties operator*() const; - - Properties *operator->(); - const Properties *operator->() const; - - operator std::vector *(); - operator const std::vector *() const; - - operator Properties *(); - operator const Properties *() const; -#endif - - protected: - std::shared_ptr d_ptr; - Properties(std::shared_ptr dd); - }; - - template - inline Properties::iterator Properties::erase_if(Predicate predicate) - { - return erase(std::remove_if(begin(), end(), predicate), end()); - } - -} diff --git a/src/core/property/hydrogenproperties_p.h b/src/core/property/hydrogenproperties_p.h deleted file mode 100644 index 317c5ff8..00000000 --- a/src/core/property/hydrogenproperties_p.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#pragma once - -#include "hydrogenproperties.h" - -namespace HYDROGEN -{ - -#ifdef HYDROGEN_PROPERTY_BACKWARD_COMPATIBILE - template - static inline std::shared_ptr make_shared_weak(T *object) - { - return std::shared_ptr(object, [](T *) {}); - } -#endif - - class PropertiesPrivate - { - public: - PropertiesPrivate(); - virtual ~PropertiesPrivate(); - - public: - std::deque properties; -#ifdef HYDROGEN_PROPERTIES_BACKWARD_COMPATIBILE - mutable std::vector propertiesBC; - Properties self{make_shared_weak(this)}; -#endif - }; - -} diff --git a/src/core/property/hydrogenproperty.cpp b/src/core/property/hydrogenproperty.cpp deleted file mode 100644 index 0a4dcb56..00000000 --- a/src/core/property/hydrogenproperty.cpp +++ /dev/null @@ -1,588 +0,0 @@ -/******************************************************************************* - Copyright(c) 2011 Jasem Mutlaq. All rights reserved. - 2022 Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#include "hydrogenproperty.h" -#include "hydrogenproperty_p.h" - -#include "basedevice.h" - -#include "hydrogenpropertytext.h" -#include "hydrogenpropertyswitch.h" -#include "hydrogenpropertynumber.h" -#include "hydrogenpropertylight.h" -#include "hydrogenpropertyblob.h" - -#include "hydrogenpropertytext_p.h" -#include "hydrogenpropertyswitch_p.h" -#include "hydrogenpropertynumber_p.h" -#include "hydrogenpropertylight_p.h" -#include "hydrogenpropertyblob_p.h" - -#include -#include - -namespace HYDROGEN -{ - - PropertyPrivate::PropertyPrivate(void *property, HYDROGEN_PROPERTY_TYPE type) - : property(property), type(property ? type : HYDROGEN_UNKNOWN), registered(property != nullptr) - { - } - - PropertyPrivate::PropertyPrivate(PropertyViewText *property) - : property(property), type(property ? HYDROGEN_TEXT : HYDROGEN_UNKNOWN), registered(property != nullptr) - { - } - - PropertyPrivate::PropertyPrivate(PropertyViewNumber *property) - : property(property), type(property ? HYDROGEN_NUMBER : HYDROGEN_UNKNOWN), registered(property != nullptr) - { - } - - PropertyPrivate::PropertyPrivate(PropertyViewSwitch *property) - : property(property), type(property ? HYDROGEN_SWITCH : HYDROGEN_UNKNOWN), registered(property != nullptr) - { - } - - PropertyPrivate::PropertyPrivate(PropertyViewLight *property) - : property(property), type(property ? HYDROGEN_LIGHT : HYDROGEN_UNKNOWN), registered(property != nullptr) - { - } - - PropertyPrivate::PropertyPrivate(PropertyViewBlob *property) - : property(property), type(property ? HYDROGEN_BLOB : HYDROGEN_UNKNOWN), registered(property != nullptr) - { - } - -#ifdef HYDROGEN_PROPERTY_BACKWARD_COMPATIBILE - HYDROGEN::Property *Property::operator->() - { - return this; - } - - const HYDROGEN::Property *Property::operator->() const - { - return this; - } - - Property::operator HYDROGEN::Property *() - { - D_PTR(Property); - return isValid() ? &d->self : nullptr; - } - - Property::operator const HYDROGEN::Property *() const - { - D_PTR(const Property); - return isValid() ? &d->self : nullptr; - } -#endif - - HYDROGEN::Property *Property::self() - { - D_PTR(Property); - return isValid() ? &d->self : nullptr; - } - -#define PROPERTY_CASE(CODE) \ - switch (d->property != nullptr ? d->type : HYDROGEN_UNKNOWN) \ - { \ - case HYDROGEN_NUMBER: \ - { \ - auto property = static_cast(d->property); \ - CODE \ - } \ - break; \ - case HYDROGEN_TEXT: \ - { \ - auto property = static_cast(d->property); \ - CODE \ - } \ - break; \ - case HYDROGEN_SWITCH: \ - { \ - auto property = static_cast(d->property); \ - CODE \ - } \ - break; \ - case HYDROGEN_LIGHT: \ - { \ - auto property = static_cast(d->property); \ - CODE \ - } \ - break; \ - case HYDROGEN_BLOB: \ - { \ - auto property = static_cast(d->property); \ - CODE \ - } \ - break; \ - default:; \ - } - - PropertyPrivate::~PropertyPrivate() - { - // Only delete properties if they were created dynamically via the buildSkeleton - // function. Other drivers are responsible for their own memory allocation. - if (property == nullptr || !dynamic) - return; - - auto d = this; - PROPERTY_CASE(delete property;) - } - - Property::Property() - : d_ptr(new PropertyPrivate(nullptr, HYDROGEN_UNKNOWN)) - { - } - - Property::Property(HYDROGEN::PropertyNumber property) - : d_ptr(property.d_ptr) - { - } - - Property::Property(HYDROGEN::PropertyText property) - : d_ptr(property.d_ptr) - { - } - - Property::Property(HYDROGEN::PropertySwitch property) - : d_ptr(property.d_ptr) - { - } - - Property::Property(HYDROGEN::PropertyLight property) - : d_ptr(property.d_ptr) - { - } - - Property::Property(HYDROGEN::PropertyBlob property) - : d_ptr(property.d_ptr) - { - } - -#ifdef HYDROGEN_PROPERTY_BACKWARD_COMPATIBILE - - Property::Property(HYDROGEN::PropertyViewNumber *property) - : d_ptr(new PropertyNumberPrivate(property)) - { - } - - Property::Property(HYDROGEN::PropertyViewText *property) - : d_ptr(new PropertyTextPrivate(property)) - { - } - - Property::Property(HYDROGEN::PropertyViewSwitch *property) - : d_ptr(new PropertySwitchPrivate(property)) - { - } - - Property::Property(HYDROGEN::PropertyViewLight *property) - : d_ptr(new PropertyLightPrivate(property)) - { - } - - Property::Property(HYDROGEN::PropertyViewBlob *property) - : d_ptr(new PropertyBlobPrivate(property)) - { - } - - Property::Property(INumberVectorProperty *property) - : d_ptr(new PropertyNumberPrivate(property)) - { - } - - Property::Property(ITextVectorProperty *property) - : d_ptr(new PropertyTextPrivate(property)) - { - } - - Property::Property(ISwitchVectorProperty *property) - : d_ptr(new PropertySwitchPrivate(property)) - { - } - - Property::Property(ILightVectorProperty *property) - : d_ptr(new PropertyLightPrivate(property)) - { - } - - Property::Property(IBLOBVectorProperty *property) - : d_ptr(new PropertyBlobPrivate(property)) - { - } -#endif - - Property::~Property() - { - } - - Property::Property(PropertyPrivate &dd) - : d_ptr(&dd) - { - } - - Property::Property(const std::shared_ptr &dd) - : d_ptr(dd) - { - } - - void Property::setProperty(void *p) - { - D_PTR(Property); - d->type = p ? d->type : HYDROGEN_UNKNOWN; - d->registered = p != nullptr; - d->property = p; - } - - void Property::setType(HYDROGEN_PROPERTY_TYPE t) - { - D_PTR(Property); - d->type = t; - } - - void Property::setRegistered(bool r) - { - D_PTR(Property); - d->registered = r; - } - - void Property::setDynamic(bool dyn) - { - D_PTR(Property); - d->dynamic = dyn; - } - - void Property::setBaseDevice(BaseDevice *idp) - { - D_PTR(Property); - d->baseDevice = (idp == nullptr ? BaseDevice() : *idp); - } - - void Property::setBaseDevice(BaseDevice baseDevice) - { - D_PTR(Property); - d->baseDevice = baseDevice; - } - - void *Property::getProperty() const - { - D_PTR(const Property); - return d->property; - } - - HYDROGEN_PROPERTY_TYPE Property::getType() const - { - D_PTR(const Property); - return d->property != nullptr ? d->type : HYDROGEN_UNKNOWN; - } - - const char *Property::getTypeAsString() const - { - switch (getType()) - { - case HYDROGEN_NUMBER: - return "HYDROGEN_NUMBER"; - case HYDROGEN_SWITCH: - return "HYDROGEN_SWITCH"; - case HYDROGEN_TEXT: - return "HYDROGEN_TEXT"; - case HYDROGEN_LIGHT: - return "HYDROGEN_LIGHT"; - case HYDROGEN_BLOB: - return "HYDROGEN_BLOB"; - case HYDROGEN_UNKNOWN: - return "HYDROGEN_UNKNOWN"; - } - return "HYDROGEN_UNKNOWN"; - } - - bool Property::getRegistered() const - { - D_PTR(const Property); - return d->registered; - } - - bool Property::isDynamic() const - { - D_PTR(const Property); - return d->dynamic; - } - - BaseDevice Property::getBaseDevice() const - { - D_PTR(const Property); - return d->baseDevice; - } - - void Property::setName(const char *name) - { - D_PTR(Property); - PROPERTY_CASE(property->setName(name);) - } - - void Property::setLabel(const char *label) - { - D_PTR(Property); - PROPERTY_CASE(property->setLabel(label);) - } - - void Property::setGroupName(const char *group) - { - D_PTR(Property); - PROPERTY_CASE(property->setGroupName(group);) - } - - void Property::setDeviceName(const char *device) - { - D_PTR(Property); - PROPERTY_CASE(property->setDeviceName(device);) - } - - void Property::setTimestamp(const char *timestamp) - { - D_PTR(Property); - PROPERTY_CASE(property->setTimestamp(timestamp);) - } - - void Property::setState(IPState state) - { - D_PTR(Property); - PROPERTY_CASE(property->setState(state);) - } - - void Property::setPermission(IPerm permission) - { - D_PTR(Property); - PROPERTY_CASE(property->setPermission(permission);) - } - - void Property::setTimeout(double timeout) - { - D_PTR(Property); - PROPERTY_CASE(property->setTimeout(timeout);) - } - - const char *Property::getName() const - { - D_PTR(const Property); - PROPERTY_CASE(return property->getName();) - return nullptr; - } - - const char *Property::getLabel() const - { - D_PTR(const Property); - PROPERTY_CASE(return property->getLabel();) - return nullptr; - } - - const char *Property::getGroupName() const - { - D_PTR(const Property); - PROPERTY_CASE(return property->getGroupName();) - return nullptr; - } - - const char *Property::getDeviceName() const - { - D_PTR(const Property); - PROPERTY_CASE(return property->getDeviceName();) - return nullptr; - } - - const char *Property::getTimestamp() const - { - D_PTR(const Property); - PROPERTY_CASE(return property->getTimestamp();) - return nullptr; - } - - IPState Property::getState() const - { - D_PTR(const Property); - PROPERTY_CASE(return property->getState();) - return IPS_ALERT; - } - - const char *Property::getStateAsString() const - { - return pstateStr(getState()); - } - - IPerm Property::getPermission() const - { - D_PTR(const Property); - PROPERTY_CASE(return property->getPermission();) - return IP_RO; - } - - bool Property::isEmpty() const - { - D_PTR(const Property); - PROPERTY_CASE(return property->isEmpty();) - return true; - } - - bool Property::isValid() const - { - D_PTR(const Property); - return d->type != HYDROGEN_UNKNOWN; - } - - bool Property::isNameMatch(const char *otherName) const - { - D_PTR(const Property); - PROPERTY_CASE(return property->isNameMatch(otherName);) - return false; - } - - bool Property::isNameMatch(const std::string &otherName) const - { - D_PTR(const Property); - PROPERTY_CASE(return property->isNameMatch(otherName);) - return false; - } - - bool Property::isLabelMatch(const char *otherLabel) const - { - D_PTR(const Property); - PROPERTY_CASE(return property->isLabelMatch(otherLabel);) - return false; - } - - bool Property::isLabelMatch(const std::string &otherLabel) const - { - D_PTR(const Property); - PROPERTY_CASE(return property->isLabelMatch(otherLabel);) - return false; - } - - bool Property::isDeviceNameMatch(const char *otherDeviceName) const - { - return isDeviceNameMatch(std::string(otherDeviceName)); - } - - bool Property::isDeviceNameMatch(const std::string &otherDeviceName) const - { - return getDeviceName() == otherDeviceName; - } - - bool Property::isTypeMatch(HYDROGEN_PROPERTY_TYPE otherType) const - { - return getType() == otherType; - } - - PropertyViewNumber *Property::getNumber() const - { - D_PTR(const Property); - if (d->type == HYDROGEN_NUMBER) - return static_cast(d->property); - - return nullptr; - } - - PropertyViewText *Property::getText() const - { - D_PTR(const Property); - if (d->type == HYDROGEN_TEXT) - return static_cast(d->property); - - return nullptr; - } - - PropertyViewLight *Property::getLight() const - { - D_PTR(const Property); - if (d->type == HYDROGEN_LIGHT) - return static_cast(d->property); - - return nullptr; - } - - PropertyViewSwitch *Property::getSwitch() const - { - D_PTR(const Property); - if (d->type == HYDROGEN_SWITCH) - return static_cast(d->property); - - return nullptr; - } - - PropertyViewBlob *Property::getBLOB() const - { - D_PTR(const Property); - if (d->type == HYDROGEN_BLOB) - return static_cast(d->property); - - return nullptr; - } - - bool Property::load() - { - D_PTR(const Property); - PROPERTY_CASE(return property->load();) - return false; - } - - void Property::save(FILE *fp) const - { - D_PTR(const Property); - PROPERTY_CASE(property->save(fp);) - } - - void Property::apply(const char *format, ...) const - { - D_PTR(const Property); - va_list ap; - va_start(ap, format); - PROPERTY_CASE(property->vapply(format, ap);) - va_end(ap); - } - - void Property::define(const char *format, ...) const - { - D_PTR(const Property); - va_list ap; - va_start(ap, format); - PROPERTY_CASE(property->vdefine(format, ap);) - va_end(ap); - } - - void Property::onUpdate(const std::function &callback) - { - D_PTR(Property); - d->onUpdateCallback = callback; - } - - void Property::emitUpdate() - { - D_PTR(Property); - if (d->onUpdateCallback) - d->onUpdateCallback(); - } - - bool Property::hasUpdateCallback() const - { - D_PTR(const Property); - return d->onUpdateCallback != nullptr; - } - -} diff --git a/src/core/property/hydrogenproperty.h b/src/core/property/hydrogenproperty.h deleted file mode 100644 index 34f8d9ad..00000000 --- a/src/core/property/hydrogenproperty.h +++ /dev/null @@ -1,251 +0,0 @@ -/******************************************************************************* - Copyright(c) 2011 Jasem Mutlaq. All rights reserved. - 2022 Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#pragma once - -#include "hydrogenbase.h" -#include "hydrogenutility.hpp" - -#include "hydrogenpropertyview.h" - -#include -#include -#include - -#define HYDROGEN_PROPERTY_BACKWARD_COMPATIBILE -namespace HYDROGEN -{ - class BaseDevice; - class PropertyNumber; - class PropertyText; - class PropertySwitch; - class PropertyLight; - class PropertyBlob; - /** - * \class HYDROGEN::Property - \brief Provides generic container for INDI properties - - \author Jasem Mutlaq - */ - class PropertyPrivate; - class Property - { - DECLARE_PRIVATE(Property) - public: - Property(); - ~Property(); - - public: - Property(HYDROGEN::PropertyNumber property); - Property(HYDROGEN::PropertyText property); - Property(HYDROGEN::PropertySwitch property); - Property(HYDROGEN::PropertyLight property); - Property(HYDROGEN::PropertyBlob property); - -#ifdef HYDROGEN_PROPERTY_BACKWARD_COMPATIBILE - public: - Property(INumberVectorProperty *property); - Property(ITextVectorProperty *property); - Property(ISwitchVectorProperty *property); - Property(ILightVectorProperty *property); - Property(IBLOBVectorProperty *property); - - public: - Property(HYDROGEN::PropertyViewNumber *property); - Property(HYDROGEN::PropertyViewText *property); - Property(HYDROGEN::PropertyViewSwitch *property); - Property(HYDROGEN::PropertyViewLight *property); - Property(HYDROGEN::PropertyViewBlob *property); - -#endif - public: - void setProperty(void *); - void setType(HYDROGEN_PROPERTY_TYPE t); - void setRegistered(bool r); - void setDynamic(bool d); - - HYDROGEN_DEPRECATED("Use setBaseDevice(BaseDevice).") - void setBaseDevice(BaseDevice *idp); - - void setBaseDevice(BaseDevice device); - - public: - void *getProperty() const; - HYDROGEN_PROPERTY_TYPE getType() const; - const char *getTypeAsString() const; - bool getRegistered() const; - bool isDynamic() const; - BaseDevice getBaseDevice() const; - - public: // Convenience Functions - void setName(const char *name); - void setLabel(const char *label); - void setGroupName(const char *groupName); - void setDeviceName(const char *deviceName); - void setTimestamp(const char *timestamp); - void setState(IPState state); - void setPermission(IPerm permission); - void setTimeout(double timeout); - - public: // Convenience Functions - const char *getName() const; - const char *getLabel() const; - const char *getGroupName() const; - const char *getDeviceName() const; - const char *getTimestamp() const; - IPState getState() const; - const char *getStateAsString() const; - IPerm getPermission() const; - - public: - bool isEmpty() const; - bool isValid() const; - - bool isNameMatch(const char *otherName) const; - bool isNameMatch(const std::string &otherName) const; - - bool isLabelMatch(const char *otherLabel) const; - bool isLabelMatch(const std::string &otherLabel) const; - - bool isDeviceNameMatch(const char *otherDeviceName) const; - bool isDeviceNameMatch(const std::string &otherDeviceName) const; - - bool isTypeMatch(HYDROGEN_PROPERTY_TYPE otherType) const; - - public: - void onUpdate(const std::function &callback); - - public: - void emitUpdate(); - bool hasUpdateCallback() const; - - public: - bool load(); - void save(FILE *fp) const; - - public: - void apply(const char *format, ...) const ATTRIBUTE_FORMAT_PRINTF(2, 3); - void define(const char *format, ...) const ATTRIBUTE_FORMAT_PRINTF(2, 3); - - void apply() const - { - apply(nullptr); - } - void define() const - { - define(nullptr); - } - - public: -#ifdef HYDROGEN_PROPERTY_BACKWARD_COMPATIBILE - HYDROGEN::PropertyViewNumber *getNumber() const; - HYDROGEN::PropertyViewText *getText() const; - HYDROGEN::PropertyViewSwitch *getSwitch() const; - HYDROGEN::PropertyViewLight *getLight() const; - HYDROGEN::PropertyViewBlob *getBLOB() const; -#endif - - public: -#ifdef HYDROGEN_PROPERTY_BACKWARD_COMPATIBILE - HYDROGEN_DEPRECATED("Do not use HYDROGEN::Property as pointer.") - HYDROGEN::Property *operator->(); - - HYDROGEN_DEPRECATED("Do not use HYDROGEN::Property as pointer.") - const HYDROGEN::Property *operator->() const; - - HYDROGEN_DEPRECATED("Do not use HYDROGEN::Property as pointer.") - operator HYDROGEN::Property *(); - - HYDROGEN_DEPRECATED("Do not use HYDROGEN::Property as pointer.") - operator const HYDROGEN::Property *() const; - - HYDROGEN_DEPRECATED("Do not use HYDROGEN::Property as pointer.") - operator HYDROGEN::PropertyViewNumber *() const - { - return getNumber(); - } - - HYDROGEN_DEPRECATED("Do not use HYDROGEN::Property as pointer.") - operator HYDROGEN::PropertyViewText *() const - { - return getText(); - } - - HYDROGEN_DEPRECATED("Do not use HYDROGEN::Property as pointer.") - operator HYDROGEN::PropertyViewSwitch *() const - { - return getSwitch(); - } - - HYDROGEN_DEPRECATED("Do not use HYDROGEN::Property as pointer.") - operator HYDROGEN::PropertyViewLight *() const - { - return getLight(); - } - - HYDROGEN_DEPRECATED("Do not use HYDROGEN::Property as pointer.") - operator HYDROGEN::PropertyViewBlob *() const - { - return getBLOB(); - } - - HYDROGEN_DEPRECATED("Use comparison to true.") - bool operator!=(std::nullptr_t) const - { - return isValid(); - } - - HYDROGEN_DEPRECATED("Use comparison to false.") - bool operator==(std::nullptr_t) const - { - return !isValid(); - } - - operator bool() const - { - return isValid(); - } - operator bool() - { - return isValid(); - } -#endif - - protected: - std::shared_ptr d_ptr; - Property(const std::shared_ptr &dd); - Property(PropertyPrivate &dd); - friend class PropertyNumber; - friend class PropertyText; - friend class PropertySwitch; - friend class PropertyLight; - friend class PropertyBlob; - - protected: - friend class BaseDevicePrivate; - HYDROGEN::Property *self(); - }; - -} // namespace HYDROGEN - -#ifdef QT_CORE_LIB -#include -Q_DECLARE_METATYPE(HYDROGEN::Property) -static int indi_property_metatype_id = QMetaTypeId::qt_metatype_id(); -#endif diff --git a/src/core/property/hydrogenproperty_p.h b/src/core/property/hydrogenproperty_p.h deleted file mode 100644 index 927eda54..00000000 --- a/src/core/property/hydrogenproperty_p.h +++ /dev/null @@ -1,69 +0,0 @@ -/******************************************************************************* - Copyright(c) 2011 Jasem Mutlaq. All rights reserved. - 2022 Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*******************************************************************************/ - -#pragma once - -#include "util/macro.hpp" -#include "hydrogenbase.h" -#include "hydrogenproperty.h" -#include "basedevice.h" -#include -#include - -namespace HYDROGEN -{ - - template - static inline std::shared_ptr property_private_cast(const std::shared_ptr &r) - { - static struct Invalid : public T - { - Invalid() : T(16) { this->type = HYDROGEN_UNKNOWN; } - } invalid; - auto result = std::dynamic_pointer_cast(r); - return result != nullptr ? result : make_shared_weak(&invalid); - } - - class BaseDevice; - class PropertyPrivate - { - public: - void *property = nullptr; - BaseDevice baseDevice; - HYDROGEN_PROPERTY_TYPE type = HYDROGEN_UNKNOWN; - bool registered = false; - bool dynamic = false; - - std::function onUpdateCallback; - - PropertyPrivate(void *property, HYDROGEN_PROPERTY_TYPE type); - PropertyPrivate(PropertyViewText *property); - PropertyPrivate(PropertyViewNumber *property); - PropertyPrivate(PropertyViewSwitch *property); - PropertyPrivate(PropertyViewLight *property); - PropertyPrivate(PropertyViewBlob *property); - - virtual ~PropertyPrivate(); - -#ifdef HYDROGEN_PROPERTY_BACKWARD_COMPATIBILE - Property self{make_shared_weak(this)}; -#endif - }; - -} diff --git a/src/core/property/hydrogenpropertybasic.cpp b/src/core/property/hydrogenpropertybasic.cpp deleted file mode 100644 index 99e5b52d..00000000 --- a/src/core/property/hydrogenpropertybasic.cpp +++ /dev/null @@ -1,464 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "hydrogenpropertybasic.h" -#include "hydrogenpropertybasic_p.h" -#include - -namespace HYDROGEN -{ - - template - PropertyBasicPrivateTemplate::PropertyBasicPrivateTemplate(size_t count) -#ifdef HYDROGEN_PROPERTY_RAW_CAST - : PropertyContainer{ - *new PropertyView()}, - PropertyPrivate(&this->typedProperty), raw{false} -#else - : PropertyPrivate(&property) -#endif - , - widgets(count) - { - this->typedProperty.setWidgets(widgets.data(), widgets.size()); - } - -#ifdef HYDROGEN_PROPERTY_RAW_CAST - template - PropertyBasicPrivateTemplate::PropertyBasicPrivateTemplate(RawPropertyType *rawProperty) - : PropertyContainer{ - *PropertyView::cast(rawProperty)}, - PropertyPrivate(PropertyView::cast(rawProperty)), raw{true} - { - } -#endif - - template - PropertyBasicPrivateTemplate::~PropertyBasicPrivateTemplate() - { -#ifdef HYDROGEN_PROPERTY_RAW_CAST - if (!raw) - delete &this->typedProperty; -#endif - } - - template - PropertyBasic::~PropertyBasic() - { - } - - template - PropertyBasic::PropertyBasic(PropertyBasicPrivate &dd) - : Property(dd) - { - } - - template - PropertyBasic::PropertyBasic(const std::shared_ptr &dd) - : Property(std::static_pointer_cast(dd)) - { - } - - template - void PropertyBasic::setName(const char *name) - { - D_PTR(PropertyBasic); - d->typedProperty.setName(name); - } - - template - void PropertyBasic::setName(const std::string &name) - { - D_PTR(PropertyBasic); - d->typedProperty.setName(name); - } - - template - void PropertyBasic::setLabel(const char *label) - { - D_PTR(PropertyBasic); - d->typedProperty.setLabel(label); - } - - template - void PropertyBasic::setLabel(const std::string &label) - { - D_PTR(PropertyBasic); - d->typedProperty.setLabel(label); - } - - template - void PropertyBasic::setGroupName(const char *name) - { - D_PTR(PropertyBasic); - d->typedProperty.setGroupName(name); - } - - template - void PropertyBasic::setGroupName(const std::string &name) - { - D_PTR(PropertyBasic); - d->typedProperty.setGroupName(name); - } - - template - void PropertyBasic::setPermission(IPerm permission) - { - D_PTR(PropertyBasic); - d->typedProperty.setPermission(permission); - } - - template - void PropertyBasic::setTimeout(double timeout) - { - D_PTR(PropertyBasic); - d->typedProperty.setTimeout(timeout); - } - - template - void PropertyBasic::setState(IPState state) - { - D_PTR(PropertyBasic); - d->typedProperty.setState(state); - } - - template - void PropertyBasic::setTimestamp(const char *timestamp) - { - D_PTR(PropertyBasic); - d->typedProperty.setTimestamp(timestamp); - } - - template - void PropertyBasic::setTimestamp(const std::string ×tamp) - { - D_PTR(PropertyBasic); - d->typedProperty.setTimestamp(timestamp); - } - - template - const char *PropertyBasic::getName() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.getName(); - } - - template - const char *PropertyBasic::getLabel() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.getLabel(); - } - - template - const char *PropertyBasic::getGroupName() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.getGroupName(); - } - - template - IPerm PropertyBasic::getPermission() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.getPermission(); - } - - template - const char *PropertyBasic::getPermissionAsString() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.getPermissionAsString(); - } - - template - double PropertyBasic::getTimeout() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.getTimeout(); - } - - template - IPState PropertyBasic::getState() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.getState(); - } - - template - const char *PropertyBasic::getStateAsString() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.getStateAsString(); - } - - template - const char *PropertyBasic::getTimestamp() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.getTimestamp(); - } - - template - bool PropertyBasic::isEmpty() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.isEmpty(); - } - - template - bool PropertyBasic::isNameMatch(const char *otherName) const - { - D_PTR(const PropertyBasic); - return d->typedProperty.isNameMatch(otherName); - } - - template - bool PropertyBasic::isNameMatch(const std::string &otherName) const - { - D_PTR(const PropertyBasic); - return d->typedProperty.isNameMatch(otherName); - } - - template - bool PropertyBasic::isLabelMatch(const char *otherLabel) const - { - D_PTR(const PropertyBasic); - return d->typedProperty.isLabelMatch(otherLabel); - } - - template - bool PropertyBasic::isLabelMatch(const std::string &otherLabel) const - { - D_PTR(const PropertyBasic); - return d->typedProperty.isLabelMatch(otherLabel); - } - - template - bool PropertyBasic::load() - { - D_PTR(const PropertyBasic); - return d->typedProperty.load(); - } - - template - void PropertyBasic::save(FILE *f) const - { - D_PTR(const PropertyBasic); - d->typedProperty.save(f); - } - - template - void PropertyBasic::vapply(const char *format, va_list args) const - { - D_PTR(const PropertyBasic); - d->typedProperty.vapply(format, args); - } - - template - void PropertyBasic::vdefine(const char *format, va_list args) const - { - D_PTR(const PropertyBasic); - d->typedProperty.vdefine(format, args); - } - - template - void PropertyBasic::apply(const char *format, ...) const - { - D_PTR(const PropertyBasic); - va_list ap; - va_start(ap, format); - d->typedProperty.vapply(format, ap); - va_end(ap); - } - - template - void PropertyBasic::define(const char *format, ...) const - { - D_PTR(const PropertyBasic); - va_list ap; - va_start(ap, format); - d->typedProperty.vdefine(format, ap); - va_end(ap); - } - - template - void PropertyBasic::apply() const - { - D_PTR(const PropertyBasic); - d->typedProperty.apply(); - } - - template - void PropertyBasic::define() const - { - D_PTR(const PropertyBasic); - d->typedProperty.define(); - } - - template - WidgetView *PropertyBasic::findWidgetByName(const char *name) const - { - D_PTR(const PropertyBasic); - return d->typedProperty.findWidgetByName(name); - } - - template - int PropertyBasic::findWidgetIndexByName(const char *name) const - { - auto it = findWidgetByName(name); - return int(it == nullptr ? -1 : it - begin()); - } - - template - size_t PropertyBasic::size() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.count(); - } - - template - void PropertyBasic::resize(size_t size) - { - D_PTR(PropertyBasic); -#ifdef HYDROGEN_PROPERTY_RAW_CAST - assert(d->raw == false); -#endif - d->widgets.resize(size); - d->typedProperty.setWidgets(d->widgets.data(), d->widgets.size()); - } - - template - void PropertyBasic::reserve(size_t size) - { - D_PTR(PropertyBasic); -#ifdef HYDROGEN_PROPERTY_RAW_CAST - assert(d->raw == false); -#endif - d->widgets.reserve(size); - d->typedProperty.setWidgets(d->widgets.data(), d->widgets.size()); - } - - template - void PropertyBasic::shrink_to_fit() - { - D_PTR(PropertyBasic); -#ifdef HYDROGEN_PROPERTY_RAW_CAST - assert(d->raw == false); -#endif - d->widgets.shrink_to_fit(); - d->typedProperty.setWidgets(d->widgets.data(), d->widgets.size()); - } - - template - void PropertyBasic::push(WidgetView &&item) - { - D_PTR(PropertyBasic); -#ifdef HYDROGEN_PROPERTY_RAW_CAST - assert(d->raw == false); -#endif - item.setParent(&d->typedProperty); - d->widgets.push_back(std::move(item)); - d->typedProperty.setWidgets(d->widgets.data(), d->widgets.size()); - } - - template - void PropertyBasic::push(const WidgetView &item) - { - push(std::move(WidgetView(item))); - } - - template - const WidgetView *PropertyBasic::at(size_t index) const - { - D_PTR(const PropertyBasic); - return d->typedProperty.at(index); - } - - template - WidgetView &PropertyBasic::operator[](ssize_t index) const - { - D_PTR(const PropertyBasic); - assert(index >= 0); - return *d->typedProperty.at(index); - } - - template - WidgetView *PropertyBasic::begin() - { - D_PTR(PropertyBasic); - return d->typedProperty.begin(); - } - - template - WidgetView *PropertyBasic::end() - { - D_PTR(PropertyBasic); - return d->typedProperty.end(); - } - - template - const WidgetView *PropertyBasic::begin() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.begin(); - } - - template - const WidgetView *PropertyBasic::end() const - { - D_PTR(const PropertyBasic); - return d->typedProperty.end(); - } - - template - PropertyView *PropertyBasic::operator&() - { - D_PTR(PropertyBasic); - return &d->typedProperty; - } - -#ifdef HYDROGEN_PROPERTY_BACKWARD_COMPATIBILE - template - PropertyView *PropertyBasic::operator->() - { - D_PTR(PropertyBasic); - return static_cast *>(static_cast(d)->property); - } - - template - HYDROGEN::PropertyView PropertyBasic::operator*() - { - D_PTR(PropertyBasic); - return *static_cast *>(static_cast(d)->property); - } -#endif - - template class PropertyBasicPrivateTemplate; - template class PropertyBasicPrivateTemplate; - template class PropertyBasicPrivateTemplate; - template class PropertyBasicPrivateTemplate; - template class PropertyBasicPrivateTemplate; - - template class PropertyBasic; - template class PropertyBasic; - template class PropertyBasic; - template class PropertyBasic; - template class PropertyBasic; - -} diff --git a/src/core/property/hydrogenpropertybasic.h b/src/core/property/hydrogenpropertybasic.h deleted file mode 100644 index 010b8462..00000000 --- a/src/core/property/hydrogenpropertybasic.h +++ /dev/null @@ -1,169 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenproperty.h" -#include "util/macro.hpp" -#include - -namespace HYDROGEN -{ - - using WidgetText = HYDROGEN::WidgetViewText; - using WidgetNumber = HYDROGEN::WidgetViewNumber; - using WidgetSwitch = HYDROGEN::WidgetViewSwitch; - using WidgetLight = HYDROGEN::WidgetViewLight; - using WidgetBlob = HYDROGEN::WidgetViewBlob; - - template - class PropertyBasicPrivateTemplate; - - template - class PropertyBasic : public HYDROGEN::Property - { - using PropertyBasicPrivate = PropertyBasicPrivateTemplate; - DECLARE_PRIVATE(PropertyBasic) - public: - using ViewType = T; - - public: - ~PropertyBasic(); - - public: - void setName(const char *name); - void setName(const std::string &name); - - void setLabel(const char *label); - void setLabel(const std::string &label); - - void setGroupName(const char *name); - void setGroupName(const std::string &name); - - void setPermission(IPerm permission); - void setTimeout(double timeout); - void setState(IPState state); - - void setTimestamp(const char *timestamp); - void setTimestamp(const std::string ×tamp); - - public: - const char *getName() const; - const char *getLabel() const; - const char *getGroupName() const; - - IPerm getPermission() const; - const char *getPermissionAsString() const; - - double getTimeout() const; - IPState getState() const; - const char *getStateAsString() const; - - const char *getTimestamp() const; - - public: - bool isEmpty() const; - - bool isNameMatch(const char *otherName) const; - bool isNameMatch(const std::string &otherName) const; - - bool isLabelMatch(const char *otherLabel) const; - bool isLabelMatch(const std::string &otherLabel) const; - - public: - /** - * @brief load Attempt to load property values from configuration file. - * @return True if value was read successfully from file, false otherwise. - */ - bool load(); - - /** - * @brief save Save property to configuration file. - * @param f Pointer to existing open configuration file. - */ - void save(FILE *f) const; - - void vapply(const char *format, va_list args) const; - void vdefine(const char *format, va_list args) const; - - void apply(const char *format, ...) const ATTRIBUTE_FORMAT_PRINTF(2, 3); - void define(const char *format, ...) const ATTRIBUTE_FORMAT_PRINTF(2, 3); - - void apply() const; - void define() const; - - protected: - PropertyView *operator&(); - - public: - size_t size() const; - size_t count() const - { - return size(); - } - - public: - void reserve(size_t size); - void resize(size_t size); - - void shrink_to_fit(); - - void push(WidgetView &&item); - void push(const WidgetView &item); - - const WidgetView *at(size_t index) const; - - WidgetView &operator[](ssize_t index) const; - - public: // STL-style iterators - WidgetView *begin(); - WidgetView *end(); - const WidgetView *begin() const; - const WidgetView *end() const; - - template - WidgetView *find_if(Predicate pred) - { - return std::find_if(begin(), end(), pred); - } - - template - const WidgetView *find_if(Predicate pred) const - { - return std::find_if(begin(), end(), pred); - } - - public: - WidgetView *findWidgetByName(const char *name) const; - int findWidgetIndexByName(const char *name) const; - - protected: - PropertyBasic(PropertyBasicPrivate &dd); - PropertyBasic(const std::shared_ptr &dd); - -#ifdef HYDROGEN_PROPERTY_BACKWARD_COMPATIBILE - public: // deprecated - HYDROGEN_DEPRECATED("Do not use HYDROGEN::PropertyXXX as pointer.") - HYDROGEN::PropertyView *operator->(); - - HYDROGEN_DEPRECATED("Do not use HYDROGEN::PropertyXXX as pointer.") - HYDROGEN::PropertyView operator*(); -#endif - }; - -} diff --git a/src/core/property/hydrogenpropertybasic_p.h b/src/core/property/hydrogenpropertybasic_p.h deleted file mode 100644 index b01cd9dd..00000000 --- a/src/core/property/hydrogenpropertybasic_p.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenproperty_p.h" -#include "hydrogenpropertyview.h" - -#include -#include - -#define HYDROGEN_PROPERTY_RAW_CAST - -namespace HYDROGEN -{ - - template - struct PropertyContainer - { -#ifndef HYDROGEN_PROPERTY_RAW_CAST - PropertyView typedProperty; -#else - PropertyView &typedProperty; -#endif - }; - template - class PropertyBasicPrivateTemplate : public PropertyContainer, public PropertyPrivate - { - public: - using RawPropertyType = typename WidgetTraits::PropertyType; - using BasicPropertyType = PropertyBasicPrivateTemplate; - - public: - PropertyBasicPrivateTemplate(size_t count); -#ifdef HYDROGEN_PROPERTY_RAW_CAST - PropertyBasicPrivateTemplate(RawPropertyType *rawProperty); -#endif - virtual ~PropertyBasicPrivateTemplate(); - - public: -#ifdef HYDROGEN_PROPERTY_RAW_CAST - bool raw; -#endif - std::vector> widgets; - }; - -} diff --git a/src/core/property/hydrogenpropertyblob.cpp b/src/core/property/hydrogenpropertyblob.cpp deleted file mode 100644 index 8f46376a..00000000 --- a/src/core/property/hydrogenpropertyblob.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "hydrogenpropertyblob.h" -#include "hydrogenpropertyblob_p.h" - -namespace HYDROGEN -{ - - PropertyBlobPrivate::PropertyBlobPrivate(size_t count) - : PropertyBasicPrivateTemplate(count) - { - } - - PropertyBlobPrivate::~PropertyBlobPrivate() - { - for (auto &it : widgets) - { - auto blob = it.getBlob(); - if (blob != nullptr && deleter != nullptr) - { - deleter(blob); - } - } - } - - PropertyBlob::PropertyBlob(size_t count) - : PropertyBasic(*new PropertyBlobPrivate(count)) - { - } - - PropertyBlob::PropertyBlob(HYDROGEN::Property property) - : PropertyBasic(property_private_cast(property.d_ptr)) - { - } - - PropertyBlob::~PropertyBlob() - { - } - - void PropertyBlob::setBlobDeleter(const std::function &deleter) - { - D_PTR(PropertyBlob); - d->deleter = deleter; - } - - bool PropertyBlob::update( - const int sizes[], const int blobsizes[], const char *const blobs[], const char *const formats[], - const char *const names[], int n) - { - D_PTR(PropertyBlob); - return d->typedProperty.update(sizes, blobsizes, blobs, formats, names, n) && (emitUpdate(), true); - } - - void PropertyBlob::fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, double timeout, IPState state) - { - D_PTR(PropertyBlob); - d->typedProperty.setWidgets(d->widgets.data(), d->widgets.size()); - d->typedProperty.fill(device, name, label, group, permission, timeout, state); - } - -} diff --git a/src/core/property/hydrogenpropertyblob.h b/src/core/property/hydrogenpropertyblob.h deleted file mode 100644 index 0457ef1b..00000000 --- a/src/core/property/hydrogenpropertyblob.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenpropertybasic.h" - -namespace HYDROGEN -{ - - class PropertyBlobPrivate; - class PropertyBlob : public HYDROGEN::PropertyBasic - { - DECLARE_PRIVATE(PropertyBlob) - public: - PropertyBlob(size_t count); - PropertyBlob(HYDROGEN::Property property); - ~PropertyBlob(); - - public: - /** - * @brief Set the Blob Deleter function - * You can define a function to release the memory of the elements. - * The function will be executed when the PropertyBlob is destroyed - * - * @param deleter function to release the memory of a given item - */ - void setBlobDeleter(const std::function &deleter); - - public: - bool update( - const int sizes[], const int blobsizes[], const char *const blobs[], const char *const formats[], - const char *const names[], int n); - - void fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, double timeout, IPState state); - }; - -} diff --git a/src/core/property/hydrogenpropertyblob_p.h b/src/core/property/hydrogenpropertyblob_p.h deleted file mode 100644 index 3059ebfb..00000000 --- a/src/core/property/hydrogenpropertyblob_p.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenpropertybasic_p.h" -#include "hydrogenpropertyview.h" - -namespace HYDROGEN -{ - - class PropertyBlobPrivate : public PropertyBasicPrivateTemplate - { - public: - PropertyBlobPrivate(size_t count); -#ifdef HYDROGEN_PROPERTY_RAW_CAST - PropertyBlobPrivate(RawPropertyType *p) - : BasicPropertyType(p) - { - } -#endif - virtual ~PropertyBlobPrivate(); - - public: - std::function deleter; - }; - -} diff --git a/src/core/property/hydrogenpropertylight.cpp b/src/core/property/hydrogenpropertylight.cpp deleted file mode 100644 index 6f42564c..00000000 --- a/src/core/property/hydrogenpropertylight.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "hydrogenpropertylight.h" -#include "hydrogenpropertylight_p.h" - -namespace HYDROGEN -{ - - PropertyLightPrivate::PropertyLightPrivate(size_t count) - : PropertyBasicPrivateTemplate(count) - { - } - - PropertyLightPrivate::~PropertyLightPrivate() - { - } - - PropertyLight::PropertyLight(size_t count) - : PropertyBasic(*new PropertyLightPrivate(count)) - { - } - - PropertyLight::PropertyLight(HYDROGEN::Property property) - : PropertyBasic(property_private_cast(property.d_ptr)) - { - } - - PropertyLight::~PropertyLight() - { - } - - void PropertyLight::fill( - const char *device, const char *name, const char *label, const char *group, - IPState state) - { - D_PTR(PropertyLight); - d->typedProperty.setWidgets(d->widgets.data(), d->widgets.size()); - d->typedProperty.fill(device, name, label, group, state); - } - -} diff --git a/src/core/property/hydrogenpropertylight.h b/src/core/property/hydrogenpropertylight.h deleted file mode 100644 index 149d5662..00000000 --- a/src/core/property/hydrogenpropertylight.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenpropertybasic.h" - -namespace HYDROGEN -{ - - class PropertyLightPrivate; - class PropertyLight : public HYDROGEN::PropertyBasic - { - DECLARE_PRIVATE(PropertyLight) - public: - PropertyLight(size_t count); - PropertyLight(HYDROGEN::Property property); - ~PropertyLight(); - - public: - // bool update(..., const char * const names[], int n); - - void fill( - const char *device, const char *name, const char *label, const char *group, - IPState state); - }; - -} diff --git a/src/core/property/hydrogenpropertylight_p.h b/src/core/property/hydrogenpropertylight_p.h deleted file mode 100644 index fee617d2..00000000 --- a/src/core/property/hydrogenpropertylight_p.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenpropertybasic_p.h" -#include "hydrogenpropertyview.h" - -namespace HYDROGEN -{ - - class PropertyLightPrivate : public PropertyBasicPrivateTemplate - { - public: - PropertyLightPrivate(size_t count); -#ifdef HYDROGEN_PROPERTY_RAW_CAST - PropertyLightPrivate(RawPropertyType *p) - : BasicPropertyType(p) - { - } -#endif - virtual ~PropertyLightPrivate(); - - public: - }; - -} diff --git a/src/core/property/hydrogenpropertynumber.cpp b/src/core/property/hydrogenpropertynumber.cpp deleted file mode 100644 index 58bfd90f..00000000 --- a/src/core/property/hydrogenpropertynumber.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "hydrogenpropertynumber.h" -#include "hydrogenpropertynumber_p.h" - -namespace HYDROGEN -{ - - PropertyNumberPrivate::PropertyNumberPrivate(size_t count) - : PropertyBasicPrivateTemplate(count) - { - } - - PropertyNumberPrivate::~PropertyNumberPrivate() - { - } - - PropertyNumber::PropertyNumber(size_t count) - : PropertyBasic(*new PropertyNumberPrivate(count)) - { - } - - PropertyNumber::PropertyNumber(HYDROGEN::Property property) - : PropertyBasic(property_private_cast(property.d_ptr)) - { - } - - PropertyNumber::~PropertyNumber() - { - } - - bool PropertyNumber::update(const double values[], const char *const names[], int n) - { - D_PTR(PropertyNumber); - return d->typedProperty.update(values, names, n) && (emitUpdate(), true); - } - - void PropertyNumber::fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, double timeout, IPState state) - { - D_PTR(PropertyNumber); - d->typedProperty.setWidgets(d->widgets.data(), d->widgets.size()); - d->typedProperty.fill(device, name, label, group, permission, timeout, state); - } - - void PropertyNumber::updateMinMax() - { - D_PTR(PropertyNumber); - d->typedProperty.updateMinMax(); - } - -} diff --git a/src/core/property/hydrogenpropertynumber.h b/src/core/property/hydrogenpropertynumber.h deleted file mode 100644 index c7679ddd..00000000 --- a/src/core/property/hydrogenpropertynumber.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenpropertybasic.h" - -namespace HYDROGEN -{ - - class PropertyNumberPrivate; - class PropertyNumber : public HYDROGEN::PropertyBasic - { - DECLARE_PRIVATE(PropertyNumber) - public: - PropertyNumber(size_t count); - PropertyNumber(HYDROGEN::Property property); - ~PropertyNumber(); - - public: - bool update(const double values[], const char *const names[], int n); - - void fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, double timeout, IPState state); - - public: - void updateMinMax(); - }; - -} diff --git a/src/core/property/hydrogenpropertynumber_p.h b/src/core/property/hydrogenpropertynumber_p.h deleted file mode 100644 index 2b7f7b7b..00000000 --- a/src/core/property/hydrogenpropertynumber_p.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenpropertybasic_p.h" -#include "hydrogenpropertyview.h" - -namespace HYDROGEN -{ - - class PropertyNumberPrivate : public PropertyBasicPrivateTemplate - { - public: - PropertyNumberPrivate(size_t count); -#ifdef HYDROGEN_PROPERTY_RAW_CAST - PropertyNumberPrivate(RawPropertyType *p) - : BasicPropertyType(p) - { - } -#endif - virtual ~PropertyNumberPrivate(); - - public: - }; - -} diff --git a/src/core/property/hydrogenpropertyswitch.cpp b/src/core/property/hydrogenpropertyswitch.cpp deleted file mode 100644 index bed7211e..00000000 --- a/src/core/property/hydrogenpropertyswitch.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "hydrogenpropertyswitch.h" -#include "hydrogenpropertyswitch_p.h" - -namespace HYDROGEN -{ - - PropertySwitchPrivate::PropertySwitchPrivate(size_t count) - : PropertyBasicPrivateTemplate(count) - { - } - - PropertySwitchPrivate::~PropertySwitchPrivate() - { - } - - PropertySwitch::PropertySwitch(size_t count) - : PropertyBasic(*new PropertySwitchPrivate(count)) - { - } - - PropertySwitch::PropertySwitch(HYDROGEN::Property property) - : PropertyBasic(property_private_cast(property.d_ptr)) - { - } - - PropertySwitch::~PropertySwitch() - { - } - - void PropertySwitch::reset() - { - D_PTR(PropertySwitch); - d->typedProperty.reset(); - } - - int PropertySwitch::findOnSwitchIndex() const - { - D_PTR(const PropertySwitch); - return d->typedProperty.findOnSwitchIndex(); - } - - HYDROGEN::WidgetViewSwitch *PropertySwitch::findOnSwitch() const - { - D_PTR(const PropertySwitch); - return d->typedProperty.findOnSwitch(); - } - - bool PropertySwitch::update(const ISState states[], const char *const names[], int n) - { - D_PTR(PropertySwitch); - if (d->onNewValuesCallback) - { - NewValues newValues; - for (int i = 0; i < n; ++i) - { - newValues[names[i]] = states[i]; - } - - d->onNewValuesCallback(newValues); - return true; - } - return d->typedProperty.update(states, names, n) && (emitUpdate(), true); - } - - bool PropertySwitch::hasUpdateCallback() const - { - D_PTR(const PropertySwitch); - return d->onNewValuesCallback != nullptr || d->onUpdateCallback != nullptr; - } - - void PropertySwitch::fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, ISRule rule, double timeout, IPState state) - { - D_PTR(PropertySwitch); - d->typedProperty.setWidgets(d->widgets.data(), d->widgets.size()); - d->typedProperty.fill(device, name, label, group, permission, rule, timeout, state); - } - - void PropertySwitch::setRule(ISRule rule) - { - D_PTR(PropertySwitch); - d->typedProperty.setRule(rule); - } - - ISRule PropertySwitch::getRule() const - { - D_PTR(const PropertySwitch); - return d->typedProperty.getRule(); - } - - const char *PropertySwitch::getRuleAsString() const - { - D_PTR(const PropertySwitch); - return d->typedProperty.getRuleAsString(); - } - - void PropertySwitch::onNewValues(const std::function &callback) - { - D_PTR(PropertySwitch); - d->onNewValuesCallback = callback; - } - -} diff --git a/src/core/property/hydrogenpropertyswitch.h b/src/core/property/hydrogenpropertyswitch.h deleted file mode 100644 index 04bd06e2..00000000 --- a/src/core/property/hydrogenpropertyswitch.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenpropertybasic.h" -#include - -namespace HYDROGEN -{ - - class PropertySwitchPrivate; - class PropertySwitch : public HYDROGEN::PropertyBasic - { - DECLARE_PRIVATE(PropertySwitch) - public: - struct NewValues : public std::map - { - bool contains(const std::string &key, const ISState &state) const - { - auto it = this->find(key); - return it != this->cend() && it->second == state; - } - }; - - public: - PropertySwitch(size_t count); - PropertySwitch(HYDROGEN::Property property); - ~PropertySwitch(); - - public: - void onNewValues(const std::function &callback); - - public: - bool update(const ISState states[], const char *const names[], int n); - bool hasUpdateCallback() const; - - void fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, ISRule rule, double timeout, IPState state); - - public: - void reset(); - int findOnSwitchIndex() const; - HYDROGEN::WidgetViewSwitch *findOnSwitch() const; - - public: - void setRule(ISRule rule); - - public: - ISRule getRule() const; - const char *getRuleAsString() const; - }; - -} diff --git a/src/core/property/hydrogenpropertyswitch_p.h b/src/core/property/hydrogenpropertyswitch_p.h deleted file mode 100644 index 92293336..00000000 --- a/src/core/property/hydrogenpropertyswitch_p.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenpropertybasic_p.h" -#include "hydrogenpropertyview.h" -#include "hydrogenpropertyswitch.h" - -#include - -namespace HYDROGEN -{ - - class PropertySwitchPrivate : public PropertyBasicPrivateTemplate - { - public: - PropertySwitchPrivate(size_t count); -#ifdef HYDROGEN_PROPERTY_RAW_CAST - PropertySwitchPrivate(RawPropertyType *p) - : BasicPropertyType(p) - { - } -#endif - virtual ~PropertySwitchPrivate(); - - public: - std::function onNewValuesCallback; - }; - -} diff --git a/src/core/property/hydrogenpropertytext.cpp b/src/core/property/hydrogenpropertytext.cpp deleted file mode 100644 index d674d548..00000000 --- a/src/core/property/hydrogenpropertytext.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "hydrogenpropertytext.h" -#include "hydrogenpropertytext_p.h" - -namespace HYDROGEN -{ - - PropertyTextPrivate::PropertyTextPrivate(size_t count) - : PropertyBasicPrivateTemplate(count) - { - } - - PropertyTextPrivate::~PropertyTextPrivate() - { - } - - PropertyText::PropertyText(size_t count) - : PropertyBasic(*new PropertyTextPrivate(count)) - { - } - - PropertyText::PropertyText(HYDROGEN::Property property) - : PropertyBasic(property_private_cast(property.d_ptr)) - { - } - - PropertyText::~PropertyText() - { - } - - bool PropertyText::update(const char *const texts[], const char *const names[], int n) - { - D_PTR(PropertyText); - return d->typedProperty.update(texts, names, n) && (emitUpdate(), true); - } - - void PropertyText::fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, double timeout, IPState state) - { - D_PTR(PropertyText); - d->typedProperty.setWidgets(d->widgets.data(), d->widgets.size()); - d->typedProperty.fill(device, name, label, group, permission, timeout, state); - } - -} diff --git a/src/core/property/hydrogenpropertytext.h b/src/core/property/hydrogenpropertytext.h deleted file mode 100644 index 94afead3..00000000 --- a/src/core/property/hydrogenpropertytext.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenpropertybasic.h" - -namespace HYDROGEN -{ - - class PropertyTextPrivate; - class PropertyText : public HYDROGEN::PropertyBasic - { - DECLARE_PRIVATE(PropertyText) - public: - PropertyText(size_t count); - PropertyText(HYDROGEN::Property property); - ~PropertyText(); - - public: - bool update(const char *const texts[], const char *const names[], int n); - - void fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, double timeout, IPState state); - }; - -} diff --git a/src/core/property/hydrogenpropertytext_p.h b/src/core/property/hydrogenpropertytext_p.h deleted file mode 100644 index 87b8d091..00000000 --- a/src/core/property/hydrogenpropertytext_p.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenpropertybasic_p.h" -#include "hydrogenpropertyview.h" - -namespace HYDROGEN -{ - - class PropertyTextPrivate : public PropertyBasicPrivateTemplate - { - public: - PropertyTextPrivate(size_t count); -#ifdef HYDROGEN_PROPERTY_RAW_CAST - PropertyTextPrivate(RawPropertyType *p) - : BasicPropertyType(p) - { - } -#endif - virtual ~PropertyTextPrivate(); - - public: - }; - -} diff --git a/src/core/property/hydrogenpropertyview.cpp b/src/core/property/hydrogenpropertyview.cpp deleted file mode 100644 index 3d7a5564..00000000 --- a/src/core/property/hydrogenpropertyview.cpp +++ /dev/null @@ -1,298 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "hydrogenpropertyview.h" - -void (*WeakIDSetTextVA)(const ITextVectorProperty *, const char *, va_list) = nullptr; -void (*WeakIDDefTextVA)(const ITextVectorProperty *, const char *, va_list) = nullptr; -void (*WeakIDSetNumberVA)(const INumberVectorProperty *, const char *, va_list) = nullptr; -void (*WeakIDDefNumberVA)(const INumberVectorProperty *, const char *, va_list) = nullptr; -void (*WeakIDSetSwitchVA)(const ISwitchVectorProperty *, const char *, va_list) = nullptr; -void (*WeakIDDefSwitchVA)(const ISwitchVectorProperty *, const char *, va_list) = nullptr; -void (*WeakIDSetLightVA)(const ILightVectorProperty *, const char *, va_list) = nullptr; -void (*WeakIDDefLightVA)(const ILightVectorProperty *, const char *, va_list) = nullptr; -void (*WeakIDSetBLOBVA)(const IBLOBVectorProperty *, const char *, va_list) = nullptr; -void (*WeakIDDefBLOBVA)(const IBLOBVectorProperty *, const char *, va_list) = nullptr; -int (*WeakIUUpdateText)(ITextVectorProperty *, char *[], char *[], int) = nullptr; -int (*WeakIUUpdateNumber)(INumberVectorProperty *, double[], char *[], int n) = nullptr; -int (*WeakIUUpdateSwitch)(ISwitchVectorProperty *, ISState *, char *[], int n) = nullptr; -int (*WeakIUUpdateBLOB)(IBLOBVectorProperty *, int[], int[], char *[], char *[], char *[], int n) = nullptr; -void (*WeakIUUpdateMinMax)(const INumberVectorProperty *) = nullptr; - -namespace HYDROGEN -{ - - static void errorUnavailable(const char *function) - { - fprintf(stderr, "%s method available only on driver side\n", function); - } - - template <> - void PropertyView::vapply(const char *format, va_list arg) const - { - if (WeakIDSetTextVA) - WeakIDSetTextVA(this, format, arg); - else - errorUnavailable(__FUNCTION__); - } - - template <> - void PropertyView::vdefine(const char *format, va_list arg) const - { - if (WeakIDDefTextVA) - WeakIDDefTextVA(this, format, arg); - else - errorUnavailable(__FUNCTION__); - } - - template <> - void PropertyView::vapply(const char *format, va_list arg) const - { - if (WeakIDSetNumberVA) - WeakIDSetNumberVA(this, format, arg); - else - errorUnavailable(__FUNCTION__); - } - - template <> - void PropertyView::vdefine(const char *format, va_list arg) const - { - if (WeakIDDefNumberVA) - WeakIDDefNumberVA(this, format, arg); - else - errorUnavailable(__FUNCTION__); - } - - template <> - void PropertyView::vapply(const char *format, va_list arg) const - { - if (WeakIDSetSwitchVA) - WeakIDSetSwitchVA(this, format, arg); - else - errorUnavailable(__FUNCTION__); - } - - template <> - void PropertyView::vdefine(const char *format, va_list arg) const - { - if (WeakIDDefSwitchVA) - WeakIDDefSwitchVA(this, format, arg); - else - errorUnavailable(__FUNCTION__); - } - - template <> - void PropertyView::vapply(const char *format, va_list arg) const - { - if (WeakIDSetLightVA) - WeakIDSetLightVA(this, format, arg); - else - errorUnavailable(__FUNCTION__); - } - - template <> - void PropertyView::vdefine(const char *format, va_list arg) const - { - if (WeakIDDefLightVA) - WeakIDDefLightVA(this, format, arg); - else - errorUnavailable(__FUNCTION__); - } - - template <> - void PropertyView::vapply(const char *format, va_list arg) const - { - if (WeakIDSetBLOBVA) - WeakIDSetBLOBVA(this, format, arg); - else - errorUnavailable(__FUNCTION__); - } - - template <> - void PropertyView::vdefine(const char *format, va_list arg) const - { - if (WeakIDDefBLOBVA) - WeakIDDefBLOBVA(this, format, arg); - else - errorUnavailable(__FUNCTION__); - } - - template - void PropertyView::apply(const char *format, ...) const - { - va_list ap; - va_start(ap, format); - this->vapply(format, ap); - va_end(ap); - } - - template - void PropertyView::define(const char *format, ...) const - { - va_list ap; - va_start(ap, format); - this->vdefine(format, ap); - va_end(ap); - } - - template <> - template <> - void PropertyView::fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, double timeout, IPState state) - { - IUFillTextVector( - this, begin(), count(), device, name, label, group, - permission, timeout, state); - } - - template <> - template <> - void PropertyView::fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, double timeout, IPState state) - { - IUFillNumberVector( - this, begin(), count(), device, name, label, group, - permission, timeout, state); - } - - template <> - template <> - void PropertyView::fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, ISRule rule, double timeout, IPState state) - { - IUFillSwitchVector( - this, begin(), count(), device, name, label, group, - permission, rule, timeout, state); - } - - template <> - template <> - void PropertyView::fill( - const char *device, const char *name, const char *label, const char *group, - IPState state) - { - IUFillLightVector( - this, begin(), count(), device, name, label, group, - state); - } - - template <> - template <> - void PropertyView::fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, double timeout, IPState state) - { - IUFillBLOBVector( - this, begin(), count(), device, name, label, group, - permission, timeout, state); - } - - template <> - template <> - bool PropertyView::update(const char *const texts[], const char *const names[], int n) - { - if (WeakIUUpdateText) - return WeakIUUpdateText(this, const_cast(texts), const_cast(names), n) == 0; - - errorUnavailable(__FUNCTION__); - return false; - } - - template <> - template <> - bool PropertyView::update(const double values[], const char *const names[], int n) - { - if (WeakIUUpdateNumber) - return WeakIUUpdateNumber(this, const_cast(values), const_cast(names), n) == 0; - - errorUnavailable(__FUNCTION__); - return false; - } - - template <> - template <> - bool PropertyView::update(const ISState states[], const char *const names[], int n) - { - if (WeakIUUpdateSwitch) - return WeakIUUpdateSwitch(this, const_cast(states), const_cast(names), n) == 0; - - errorUnavailable(__FUNCTION__); - return false; - } - - template <> - template <> - bool PropertyView::update( - const int sizes[], const int blobsizes[], const char *const blobs[], const char *const formats[], - const char *const names[], int n) - { - if (WeakIUUpdateBLOB) - return WeakIUUpdateBLOB( - this, - const_cast(sizes), const_cast(blobsizes), - const_cast(blobs), const_cast(formats), - const_cast(names), n) == 0; - - errorUnavailable(__FUNCTION__); - return false; - } - - template <> - template <> - void PropertyView::updateMinMax() - { - if (WeakIUUpdateMinMax) - WeakIUUpdateMinMax(this); - } - - void WidgetView::fill(const char *name, const char *label, const char *initialText) - { - IUFillText(this, name, label, initialText); - } - - void WidgetView::fill(const char *name, const char *label, ISState state) - { - IUFillSwitch(this, name, label, state); - } - - void WidgetView::fill(const char *name, const char *label, IPState state) - { - IUFillLight(this, name, label, state); - } - - void WidgetView::fill(const char *name, const char *label, const char *format, - double min, double max, double step, double value) - { - IUFillNumber(this, name, label, format, min, max, step, value); - } - - void WidgetView::fill(const char *name, const char *label, const char *format) - { - IUFillBLOB(this, name, label, format); - } - - template struct PropertyView; - template struct PropertyView; - template struct PropertyView; - template struct PropertyView; - template struct PropertyView; - -} diff --git a/src/core/property/hydrogenpropertyview.h b/src/core/property/hydrogenpropertyview.h deleted file mode 100644 index fae5c31b..00000000 --- a/src/core/property/hydrogenpropertyview.h +++ /dev/null @@ -1,1430 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenapi.h" -#include "hydrogendevapi.hpp" -#include "hydrogenwidgettraits.h" -#include "hydrogenwidgetview.h" - -#include -#include -#include -#include -#include - -namespace HYDROGEN -{ - - template - struct WidgetView; - template - struct PropertyView; - - typedef PropertyView PropertyViewText; - typedef PropertyView PropertyViewNumber; - typedef PropertyView PropertyViewSwitch; - typedef PropertyView PropertyViewLight; - typedef PropertyView PropertyViewBlob; - - typedef WidgetView WidgetViewText; - typedef WidgetView WidgetViewNumber; - typedef WidgetView WidgetViewSwitch; - typedef WidgetView WidgetViewLight; - typedef WidgetView WidgetViewBlob; - -#define PROPERTYVIEW_BASE_ACCESS public - // don't use direct access to low-level property - // #define PROPERTYVIEW_BASE_ACCESS protected // future - - /** - * \class HYDROGEN::PropertyView and HYDROGEN::WidgetView - * \brief Provides decorator for Low-Level IXXXVectorProperty/IXXX - * - * HYDROGEN::PropertyView - * - * A class that will allow a easy transition to the new widget handling interface (future). - * - Use PropertyViewText instead of ITextVectorProperty - * - Use PropertyViewNumber instead of INumberVectorProperty - * - Use PropertyViewSwitch instead of ISwitchVectorProperty - * - Use PropertyViewLight instead of ILightVectorProperty - * - Use PropertyViewBlob instead of IBLOBVectorProperty - * - * The PropertyView class is compatible with low-level IXXXVectorProperty structures. - * - * HYDROGEN::WidgetView - * - * A class that will allow a easy transition to the new widget handling interface (future). - * - Use WidgetViewText instead of IText - * - Use WidgetViewNumber instead of INumber - * - Use WidgetViewSwitch instead of ISwitch - * - Use WidgetViewLight instead of ILight - * - Use WidgetViewBlob instead of IBLOB - * - * The WidgetView class is compatible with low-level IXXX structures. - */ - - template - struct PropertyView : PROPERTYVIEW_BASE_ACCESS WidgetTraits::PropertyType - { - using Type = T; - using PropertyType = typename WidgetTraits::PropertyType; - using WidgetType = WidgetView; - - friend class Property; - friend class PropertyPrivate; - friend class BaseDevice; - friend class DefaultDevice; - template - friend struct WidgetView; - - template - using enable_if_is_same_t = typename std::enable_if::value, bool>::type; - - public: - PropertyView(); - - // #PS: do not delete items, they may be on the stack. - //~PropertyView() { for(auto &p: *this) {p.clear();} free(widget()); } - - public: - void setDeviceName(const char *name); /* outside implementation */ - void setDeviceName(const std::string &name); /* outside implementation */ - - void setName(const char *name); /* outside implementation */ - void setName(const std::string &name); /* outside implementation */ - - void setLabel(const char *label); /* outside implementation */ - void setLabel(const std::string &label); /* outside implementation */ - - void setGroupName(const char *name); /* outside implementation */ - void setGroupName(const std::string &name); /* outside implementation */ - - void setPermission(IPerm permission); /* outside implementation */ - void setTimeout(double timeout); /* outside implementation */ - void setState(IPState state); - - void setTimestamp(const char *timestamp); /* outside implementation */ - void setTimestamp(const std::string ×tamp); /* outside implementation */ - - void setAux(void *user); /* outside implementation */ - void setWidgets(WidgetType *w, size_t count); /* outside implementation */ - - template - void setWidgets(WidgetType (&w)[N]); /* outside implementation */ - - public: // only for ISwitch - void setRule(ISRule rule); /* outside implementation */ - bool setRule(const std::string &rule); /* outside implementation */ - - template = true> - void reset() - { - IUResetSwitch(this); - } - - template = true> - WidgetType *findOnSwitch() const - { - return static_cast(IUFindOnSwitch(this)); - } - - template = true> - int findOnSwitchIndex() const - { - return IUFindOnSwitchIndex(this); - } - - public: // only for INumber - template = true> - void updateMinMax(); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - public: // getters - const char *getDeviceName() const - { - return this->device; - } - const char *getName() const - { - return this->name; - } - const char *getLabel() const - { - return this->label; - } - const char *getGroupName() const - { - return this->group; - } - - IPerm getPermission() const; /* outside implementation */ - const char *getPermissionAsString() const - { - return permStr(getPermission()); - } - - ISRule getRule() const; /* outside implementation */ - const char *getRuleAsString() const - { - return ruleStr(getRule()); - } - - double getTimeout() const; /* outside implementation */ - IPState getState() const - { - return this->s; - } - const char *getStateAsString() const - { - return pstateStr(getState()); - } - - const char *getTimestamp() const - { - return this->timestamp; - } - void *getAux() const - { - return this->aux; - } - - int count() const; /* outside implementation */ - WidgetType *widget() const; /* outside implementation */ - - WidgetType *findWidgetByName(const char *name) const; /* outside implementation */ - - public: // tests - bool isEmpty() const - { - return widget() == nullptr || count() == 0; - } - - bool isNameMatch(const char *otherName) const - { - return !strcmp(getName(), otherName); - } - bool isNameMatch(const std::string &otherName) const - { - return getName() == otherName; - } - - bool isLabelMatch(const char *otherLabel) const - { - return !strcmp(getLabel(), otherLabel); - } - bool isLabelMatch(const std::string &otherLabel) const - { - return getLabel() == otherLabel; - } - - public: // only driver side - bool load(); - void save(FILE *f) const; /* outside implementation */ - - void vapply(const char *format, va_list args) - const; /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - void vdefine(const char *format, va_list args) - const; /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - void apply(const char *format, ...) const ATTRIBUTE_FORMAT_PRINTF(2, - 3); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - void define(const char *format, ...) const ATTRIBUTE_FORMAT_PRINTF(2, - 3); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - void apply() const - { - apply(nullptr); - } - void define() const - { - define(nullptr); - } - - public: - template = true> - void fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, double timeout, IPState state); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - template = true> - void fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, double timeout, IPState state); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - template = true> - void fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, ISRule rule, double timeout, IPState state); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - template = true> - void fill( - const char *device, const char *name, const char *label, const char *group, - IPState state); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - template = true> - void fill( - const char *device, const char *name, const char *label, const char *group, - IPerm permission, double timeout, IPState state); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - public: - template = true> - bool update(const char *const texts[], const char *const names[], int n); - /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - template = true> - bool update(const double values[], const char *const names[], int n); - /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - template = true> - bool update(const ISState states[], const char *const names[], int n); - /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - /* - template = true> - bool update(..., const char * const names[], int n); - */ - - template = true> - bool update( - const int sizes[], const int blobsizes[], const char *const blobs[], const char *const formats[], - const char *const names[], int n); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - public: - WidgetType *begin() const - { - return widget(); - } - WidgetType *end() const - { - return widget() + count(); - } - - WidgetType *at(size_t index) const - { - return widget() + index; - } - - public: - void clear() - { - for (auto &p : *this) - { - p.clear(); - } - // free(widget()); // #PS: do not delete items, they may be on the stack. - memset(this, 0, sizeof(*this)); - } - - public: // internal use only - static PropertyView *cast(PropertyType *raw) - { - return static_cast *>(raw); - } - PropertyType *cast() - { - return this; - } - }; - - template - struct WidgetView; - - template <> - struct WidgetView : PROPERTYVIEW_BASE_ACCESS IText - { - using Type = IText; - template - friend struct PropertyView; - - public: - WidgetView() - { - memset(this, 0, sizeof(*this)); - } - WidgetView(const WidgetView &other) : Type(other) - { - this->text = nullptr; - setText(other.text); - } - WidgetView(WidgetView &&other) : Type(other) - { - memset(static_cast(&other), 0, sizeof(other)); - } - WidgetView &operator=(const WidgetView &other) - { - return *this = WidgetView(other); - } - WidgetView &operator=(WidgetView &&other) - { - std::swap(static_cast(other), static_cast(*this)); - return *this; - } - ~WidgetView() - { - free(this->text); - } - void clear() - { - free(this->text); - memset(this, 0, sizeof(*this)); - } - // bool isNull() const { return reinterpret_cast(this) == nullptr; } - - public: // setters - void setParent(ITextVectorProperty *parent) - { - this->tvp = parent; - } - void setParent(PropertyView *parent) - { - this->tvp = static_cast(parent); - } - - void setName(const char *name) - { - HYDROGEN::strlcpy(this->name, name); - } - void setName(const std::string &name) - { - setName(name.data()); - } - - void setLabel(const char *label) - { - HYDROGEN::strlcpy(this->label, label); - } - void setLabel(const std::string &label) - { - setLabel(label.data()); - } - - // void setText(const char *text) { free(this->text); this->text = strndup(text, strlen(text)); } - void setText(const char *text, size_t size) - { - HYDROGEN::strlcpy(this->text = static_cast(realloc(this->text, size + 1)), text, size + 1); - } - void setText(const char *text) - { - setText(text, strlen(text)); - } - void setText(const std::string &text) - { - setText(text.data(), text.size()); - } - - void setAux(void *user) - { - this->aux0 = user; - } - // don't use any other aux! - - public: // getters - const char *getName() const - { - return this->name; - } - const char *getLabel() const - { - return this->label; - } - const char *getText() const - { - return this->text ? this->text : ""; - } - - void *getAux() const - { - return this->aux0; - } - - public: // tests - bool isNameMatch(const char *otherName) const - { - return !strcmp(getName(), otherName); - } - bool isNameMatch(const std::string &otherName) const - { - return getName() == otherName; - } - - bool isLabelMatch(const char *otherLabel) const - { - return !strcmp(getLabel(), otherLabel); - } - bool isLabelMatch(const std::string &otherLabel) const - { - return getLabel() == otherLabel; - } - - public: - void fill(const char *name, const char *label, const char *initialText); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - void fill(const std::string &name, const std::string &label, const std::string &initialText) - { - fill(name.c_str(), label.c_str(), initialText.c_str()); - } - }; - - template <> - struct WidgetView : PROPERTYVIEW_BASE_ACCESS INumber - { - using Type = INumber; - template - friend struct PropertyView; - - public: - WidgetView() - { - memset(this, 0, sizeof(*this)); - } - WidgetView(const WidgetView &other) : Type(other) {} - WidgetView(WidgetView &&other) : Type(other) - { - memset(static_cast(&other), 0, sizeof(other)); - } - WidgetView &operator=(const WidgetView &other) - { - return *this = WidgetView(other); - } - WidgetView &operator=(WidgetView &&other) - { - std::swap(static_cast(other), static_cast(*this)); - return *this; - } - ~WidgetView() {} - void clear() - { - memset(this, 0, sizeof(*this)); - } - // bool isNull() const { return reinterpret_cast(this) == nullptr; } - - public: // setters - void setParent(INumberVectorProperty *parent) - { - this->nvp = parent; - } - void setParent(PropertyView *parent) - { - this->nvp = static_cast(parent); - } - - void setName(const char *name) - { - HYDROGEN::strlcpy(this->name, name); - } - void setName(const std::string &name) - { - setName(name.data()); - } - - void setLabel(const char *label) - { - HYDROGEN::strlcpy(this->label, label); - } - void setLabel(const std::string &label) - { - setLabel(label.data()); - } - - void setFormat(const char *format) - { - HYDROGEN::strlcpy(this->format, format); - } - void setFormat(const std::string &format) - { - setLabel(format.data()); - } - - void setMin(double min) - { - this->min = min; - } - void setMax(double max) - { - this->max = max; - } - void setMinMax(double min, double max) - { - setMin(min); - setMax(max); - } - void setStep(double step) - { - this->step = step; - } - void setValue(double value) - { - this->value = value; - } - - void setAux(void *user) - { - this->aux0 = user; - } - // don't use any other aux! - - public: // getters - const char *getName() const - { - return this->name; - } - const char *getLabel() const - { - return this->label; - } - const char *getFormat() const - { - return this->format; - } - - double getMin() const - { - return this->min; - } - double getMax() const - { - return this->max; - } - double getStep() const - { - return this->step; - } - double getValue() const - { - return this->value; - } - - void *getAux() const - { - return this->aux0; - } - - public: // tests - bool isNameMatch(const char *otherName) const - { - return !strcmp(getName(), otherName); - } - bool isNameMatch(const std::string &otherName) const - { - return getName() == otherName; - } - - bool isLabelMatch(const char *otherLabel) const - { - return !strcmp(getLabel(), otherLabel); - } - bool isLabelMatch(const std::string &otherLabel) const - { - return getLabel() == otherLabel; - } - - public: - void fill(const char *name, const char *label, const char *format, - double min, double max, double step, double value); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - void fill(const std::string &name, const std::string &label, const std::string &format, - double min, double max, double step, double value) - { - fill(name.c_str(), label.c_str(), format.c_str(), min, max, step, value); - } - }; - - template <> - struct WidgetView : PROPERTYVIEW_BASE_ACCESS ISwitch - { - using Type = ISwitch; - template - friend struct PropertyView; - - public: - WidgetView() - { - memset(this, 0, sizeof(*this)); - } - WidgetView(const WidgetView &other) : Type(other) {} - WidgetView(WidgetView &&other) : Type(other) - { - memset(static_cast(&other), 0, sizeof(other)); - } - WidgetView &operator=(const WidgetView &other) - { - return *this = WidgetView(other); - } - WidgetView &operator=(WidgetView &&other) - { - std::swap(static_cast(other), static_cast(*this)); - return *this; - } - ~WidgetView() {} - void clear() - { - memset(this, 0, sizeof(*this)); - } - // bool isNull() const { return reinterpret_cast(this) == nullptr; } - - public: // setters - void setParent(ISwitchVectorProperty *parent) - { - this->svp = parent; - } - void setParent(PropertyView *parent) - { - this->svp = static_cast(parent); - } - - void setName(const char *name) - { - HYDROGEN::strlcpy(this->name, name); - } - void setName(const std::string &name) - { - setName(name.data()); - } - - void setLabel(const char *label) - { - HYDROGEN::strlcpy(this->label, label); - } - void setLabel(const std::string &label) - { - setLabel(label.data()); - } - - void setState(const ISState &state) - { - this->s = state; - } - bool setState(const std::string &state) - { - return crackISState(state.data(), &this->s) == 0; - } - - void setAux(void *user) - { - this->aux = user; - } - // don't use any other aux! - - public: // getters - const char *getName() const - { - return this->name; - } - const char *getLabel() const - { - return this->label; - } - - ISState getState() const - { - return this->s; - } - const char *getStateAsString() const - { - return sstateStr(getState()); - } - - void *getAux() const - { - return this->aux; - } - - public: // tests - bool isNameMatch(const char *otherName) const - { - return !strcmp(getName(), otherName); - } - bool isNameMatch(const std::string &otherName) const - { - return getName() == otherName; - } - - bool isLabelMatch(const char *otherLabel) const - { - return !strcmp(getLabel(), otherLabel); - } - bool isLabelMatch(const std::string &otherLabel) const - { - return getLabel() == otherLabel; - } - - public: - void fill(const char *name, const char *label, ISState state = ISS_OFF); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - void fill(const std::string &name, const std::string &label, ISState state = ISS_OFF) - { - fill(name.c_str(), label.c_str(), state); - } - }; - - template <> - struct WidgetView : PROPERTYVIEW_BASE_ACCESS ILight - { - using Type = ILight; - template - friend struct PropertyView; - - public: - WidgetView() - { - memset(this, 0, sizeof(*this)); - } - WidgetView(const WidgetView &other) : Type(other) {} - WidgetView(WidgetView &&other) : Type(other) - { - memset(static_cast(&other), 0, sizeof(other)); - } - WidgetView &operator=(const WidgetView &other) - { - return *this = WidgetView(other); - } - WidgetView &operator=(WidgetView &&other) - { - std::swap(static_cast(other), static_cast(*this)); - return *this; - } - ~WidgetView() {} - void clear() - { - memset(this, 0, sizeof(*this)); - } - // bool isNull() const { return reinterpret_cast(this) == nullptr; } - - public: // setters - void setParent(ILightVectorProperty *parent) - { - this->lvp = parent; - } - void setParent(PropertyView *parent) - { - this->lvp = static_cast(parent); - } - - void setName(const char *name) - { - HYDROGEN::strlcpy(this->name, name); - } - void setName(const std::string &name) - { - setName(name.data()); - } - - void setLabel(const char *label) - { - HYDROGEN::strlcpy(this->label, label); - } - void setLabel(const std::string &label) - { - setLabel(label.data()); - } - - void setState(const IPState &state) - { - this->s = state; - } - bool setState(const std::string &state) - { - return crackIPState(state.data(), &this->s) == 0; - } - - void setAux(void *user) - { - this->aux = user; - } - // don't use any other aux! - - public: // getters - const char *getName() const - { - return this->name; - } - const char *getLabel() const - { - return this->label; - } - - IPState getState() const - { - return this->s; - } - const char *getStateAsString() const - { - return pstateStr(getState()); - } - - void *getAux() const - { - return this->aux; - } - - public: // tests - bool isNameMatch(const char *otherName) const - { - return !strcmp(getName(), otherName); - } - bool isNameMatch(const std::string &otherName) const - { - return getName() == otherName; - } - - bool isLabelMatch(const char *otherLabel) const - { - return !strcmp(getLabel(), otherLabel); - } - bool isLabelMatch(const std::string &otherLabel) const - { - return getLabel() == otherLabel; - } - - public: - void fill(const char *name, const char *label, IPState state = IPS_OK); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - void fill(const std::string &name, const std::string &&label, IPState state = IPS_OK) - { - fill(name.c_str(), label.c_str(), state); - } - }; - - template <> - struct WidgetView : PROPERTYVIEW_BASE_ACCESS IBLOB - { - using Type = IBLOB; - template - friend struct PropertyView; - - public: - WidgetView() - { - memset(this, 0, sizeof(*this)); - } - WidgetView(const WidgetView &other) : Type(other) {} - WidgetView(WidgetView &&other) : Type(other) - { - memset(static_cast(&other), 0, sizeof(other)); - } - WidgetView &operator=(const WidgetView &other) - { - return *this = WidgetView(other); - } - WidgetView &operator=(WidgetView &&other) - { - std::swap(static_cast(other), static_cast(*this)); - return *this; - } - ~WidgetView() - { /* free(this->blob); */ - } - void clear() - { - /* free(this->blob); */ memset(this, 0, sizeof(*this)); - } - // bool isNull() const { return reinterpret_cast(this) == nullptr; } - - public: // setters - void setParent(IBLOBVectorProperty *parent) - { - this->bvp = parent; - } - void setParent(PropertyView *parent) - { - this->bvp = static_cast(parent); - } - - void setName(const char *name) - { - HYDROGEN::strlcpy(this->name, name); - } - void setName(const std::string &name) - { - setName(name.data()); - } - - void setLabel(const char *label) - { - HYDROGEN::strlcpy(this->label, label); - } - void setLabel(const std::string &label) - { - setLabel(label.data()); - } - - void setFormat(const char *format) - { - HYDROGEN::strlcpy(this->format, format); - } - void setFormat(const std::string &format) - { - setLabel(format.data()); - } - - void setBlob(void *blob) - { - this->blob = blob; - } - void setBlobLen(int size) - { - this->bloblen = size; - } - void setSize(int size) - { - this->size = size; - } - - void setAux(void *user) - { - this->aux0 = user; - } - // don't use any other aux! - - public: // getters - const char *getName() const - { - return this->name; - } - const char *getLabel() const - { - return this->label; - } - const char *getFormat() const - { - return this->format; - } - void *getBlob() - { - return this->blob; - } - const void *getBlob() const - { - return this->blob; - } - std::string getBlobAsString() const - { - return std::string(static_cast(this->blob), this->bloblen); - } - int getBlobLen() const - { - return this->bloblen; - } - int getSize() const - { - return this->size; - } - - void *getAux() const - { - return this->aux0; - } - - public: // tests - bool isNameMatch(const char *otherName) const - { - return !strcmp(getName(), otherName); - } - bool isNameMatch(const std::string &otherName) const - { - return getName() == otherName; - } - - bool isLabelMatch(const char *otherLabel) const - { - return !strcmp(getLabel(), otherLabel); - } - bool isLabelMatch(const std::string &otherLabel) const - { - return getLabel() == otherLabel; - } - - public: - void fill(const char *name, const char *label, const char *format); /* outside implementation - only driver side, see indipropertyview_driver.cpp */ - - void fill(const std::string &name, const std::string &label, const std::string &format) - { - fill(name.c_str(), label.c_str(), format.c_str()); - } - - public: // internal use only - static WidgetView *cast(Type *blob) - { - return static_cast *>(blob); - } - Type *cast() - { - return this; - } - }; - - /* outside implementation */ - template - inline PropertyView::PropertyView() - { - memset(this, 0, sizeof(*this)); - } - - template - inline void PropertyView::setDeviceName(const char *name) - { - HYDROGEN::strlcpy(this->device, name); - } - - template - inline void PropertyView::setDeviceName(const std::string &name) - { - setDeviceName(name.data()); - } - - template - inline void PropertyView::setName(const char *name) - { - HYDROGEN::strlcpy(this->name, name); - } - - template - inline void PropertyView::setName(const std::string &name) - { - setName(name.data()); - } - - template - inline void PropertyView::setLabel(const char *label) - { - HYDROGEN::strlcpy(this->label, label); - } - - template - inline void PropertyView::setLabel(const std::string &label) - { - setLabel(label.data()); - } - - template - inline void PropertyView::setGroupName(const char *name) - { - HYDROGEN::strlcpy(this->group, name); - } - - template - inline void PropertyView::setGroupName(const std::string &name) - { - setGroupName(name.data()); - } - - template - inline void PropertyView::setState(IPState state) - { - this->s = state; - } - - template - inline void PropertyView::setTimestamp(const char *timestamp) - { - HYDROGEN::strlcpy(this->timestamp, timestamp); - } - - template - inline void PropertyView::setTimestamp(const std::string ×tamp) - { - setTimestamp(timestamp.data()); - } - - template - template - inline void PropertyView::setWidgets(WidgetType (&w)[N]) - { - setWidgets(static_cast(w), N); - } - - template - inline void PropertyView::setAux(void *user) - { - this->aux = user; - } - - template <> - inline bool PropertyView::load() - { - return IULoadConfigNumber(this) == nnp; - } - - template <> - inline bool PropertyView::load() - { - return IULoadConfigSwitch(this) == nsp; - } - - template <> - inline bool PropertyView::load() - { - return false; - } - - template <> - inline bool PropertyView::load() - { - return false; - } - - template <> - inline bool PropertyView::load() - { - return IULoadConfigText(this) == ntp; - } - - template <> - inline void PropertyView::save(FILE *f) const - { - IUSaveConfigNumber(f, this); - } - - template <> - inline void PropertyView::save(FILE *f) const - { - IUSaveConfigText(f, this); - } - - template <> - inline void PropertyView::save(FILE *f) const - { - IUSaveConfigSwitch(f, this); - } - - template <> - inline void PropertyView::save(FILE *f) const - { - (void)f; /* IUSaveConfigLight(f, this); */ - } - - template <> - inline void PropertyView::save(FILE *f) const - { - IUSaveConfigBLOB(f, this); - } - - template - inline void PropertyView::setTimeout(double timeout) - { - this->timeout = timeout; - } - - template <> - inline void PropertyView::setTimeout(double) - { - } - - template - inline void PropertyView::setPermission(IPerm permission) - { - this->p = permission; - } - - template <> - inline void PropertyView::setPermission(IPerm) - { - } - - template - inline void PropertyView::setRule(ISRule) - { - } - - template <> - inline void PropertyView::setRule(ISRule rule) - { - this->r = rule; - } - - template - inline bool PropertyView::setRule(const std::string &) - { - return false; - } - - template <> - inline bool PropertyView::setRule(const std::string &rule) - { - return crackISRule(rule.data(), &this->r) == 0; - } - - template - inline WidgetView *PropertyView::findWidgetByName(const char *) const - { - return nullptr; - } - - template <> - inline WidgetView *PropertyView::findWidgetByName(const char *name) const - { - return static_cast *>(IUFindText(this, name)); - } - - template <> - inline WidgetView *PropertyView::findWidgetByName(const char *name) const - { - return static_cast *>(IUFindNumber(this, name)); - } - - template <> - inline WidgetView *PropertyView::findWidgetByName(const char *name) const - { - return static_cast *>(IUFindSwitch(this, name)); - } - - template <> - inline WidgetView *PropertyView::findWidgetByName(const char *name) const - { - return static_cast *>(IUFindLight(this, name)); - } - - template <> - inline WidgetView *PropertyView::findWidgetByName(const char *name) const - { - return static_cast *>(IUFindBLOB(this, name)); - } - - template - inline IPerm PropertyView::getPermission() const - { - return this->p; - } - - template <> - inline IPerm PropertyView::getPermission() const - { - return IP_RO; - } - - template - inline ISRule PropertyView::getRule() const - { - return ISR_NOFMANY; - } - - template <> - inline ISRule PropertyView::getRule() const - { - return this->r; - } - - template - inline double PropertyView::getTimeout() const - { - return this->timeout; - } - - template <> - inline double PropertyView::getTimeout() const - { - return 0; - } - - template <> - inline void PropertyView::setWidgets(WidgetType *w, size_t size) - { - this->tp = w; - this->ntp = int(size); - } - - template <> - inline void PropertyView::setWidgets(WidgetType *w, size_t size) - { - this->np = w; - this->nnp = int(size); - } - - template <> - inline void PropertyView::setWidgets(WidgetType *w, size_t size) - { - this->sp = w; - this->nsp = int(size); - } - - template <> - inline void PropertyView::setWidgets(WidgetType *w, size_t size) - { - this->lp = w; - this->nlp = int(size); - } - - template <> - inline void PropertyView::setWidgets(WidgetType *w, size_t size) - { - this->bp = w; - this->nbp = int(size); - } - - template <> - inline int PropertyView::count() const - { - return this->ntp; - } - - template <> - inline int PropertyView::count() const - { - return this->nnp; - } - - template <> - inline int PropertyView::count() const - { - return this->nsp; - } - - template <> - inline int PropertyView::count() const - { - return this->nlp; - } - - template <> - inline int PropertyView::count() const - { - return this->nbp; - } - - template <> - inline PropertyView::WidgetType *PropertyView::widget() const - { - return static_cast(this->tp); - } - - template <> - inline PropertyView::WidgetType *PropertyView::widget() const - { - return static_cast(this->np); - } - - template <> - inline PropertyView::WidgetType *PropertyView::widget() const - { - return static_cast(this->sp); - } - - template <> - inline PropertyView::WidgetType *PropertyView::widget() const - { - return static_cast(this->lp); - } - - template <> - inline PropertyView::WidgetType *PropertyView::widget() const - { - return static_cast(this->bp); - } - -} diff --git a/src/core/property/hydrogenwidgettraits.h b/src/core/property/hydrogenwidgettraits.h deleted file mode 100644 index 75274e41..00000000 --- a/src/core/property/hydrogenwidgettraits.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include "hydrogenapi.h" -#include "hydrogenutility.hpp" - -namespace HYDROGEN -{ - - template - struct WidgetTraits; - - template <> - struct WidgetTraits - { - using PropertyType = ITextVectorProperty; - struct UpdateArgs - { - char **texts; - char **names; - int n; - }; - }; - - template <> - struct WidgetTraits - { - using PropertyType = INumberVectorProperty; - struct UpdateArgs - { - double *values; - char **names; - int n; - }; - }; - - template <> - struct WidgetTraits - { - using PropertyType = ISwitchVectorProperty; - struct UpdateArgs - { - ISState *states; - char **names; - int n; - }; - }; - - template <> - struct WidgetTraits - { - using PropertyType = ILightVectorProperty; - struct UpdateArgs - { - }; - }; - - template <> - struct WidgetTraits - { - using PropertyType = IBLOBVectorProperty; - struct UpdateArgs - { - int *sizes; - int *blobsizes; - char **blobs; - char **formats; - char **names; - int n; - }; - }; - -} diff --git a/src/core/property/hydrogenwidgetview.h b/src/core/property/hydrogenwidgetview.h deleted file mode 100644 index a0a9019c..00000000 --- a/src/core/property/hydrogenwidgetview.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - Copyright (C) 2021 by Pawel Soja - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include "hydrogenpropertyview.h" From 11fb49539347bf754af033ab399339119b1c5ceb Mon Sep 17 00:00:00 2001 From: Max Qian Date: Wed, 13 Dec 2023 21:00:40 +0800 Subject: [PATCH 6/9] update --- CMakeLists.txt | 20 +- locale/lithium.pot | 69 ++++--- locale/po/en_US.UTF-8/lithium.po | 67 ++++--- modules | 2 +- src/atom/server/message_bus.hpp | 14 +- src/atom/utils/type.hpp | 170 ++++++++++++++++ src/client/CMakeLists.txt | 11 ++ src/client/hydrogen/CMakeLists.txt | 59 ++++++ src/device/device_manager.cpp | 26 +-- src/device/device_manager.hpp | 19 +- src/device/hydrogen_device.cpp | 168 ++++++++++++++++ src/device/hydrogen_device.hpp | 116 +++++++++++ ...evice_manager.cpp => hydrogen_manager.cpp} | 72 +++---- ...evice_manager.hpp => hydrogen_manager.hpp} | 56 +++--- src/device/indi_device.cpp | 168 ---------------- src/device/indi_device.hpp | 116 ----------- src/websocket/template/function.hpp | 24 ++- tests/atom/messagebus.cpp | 186 ++++++++++++++++++ 18 files changed, 907 insertions(+), 456 deletions(-) create mode 100644 src/atom/utils/type.hpp create mode 100644 src/client/CMakeLists.txt create mode 100644 src/client/hydrogen/CMakeLists.txt rename src/device/{indidevice_manager.cpp => hydrogen_manager.cpp} (74%) rename src/device/{indidevice_manager.hpp => hydrogen_manager.hpp} (60%) delete mode 100644 src/device/indi_device.cpp delete mode 100644 src/device/indi_device.hpp create mode 100644 tests/atom/messagebus.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 48b87b15..c5eebb48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,26 +115,14 @@ elseif(LINUX) set(CMAKE_INSTALL_PREFIX "/usr/lithium") endif() -set(api_module - ${lithium_client_dir}/platesolve/astap.cpp - ${lithium_client_dir}/platesolve/astrometry.cpp - ${lithium_client_dir}/phd2/phd2client.cpp - ${lithium_client_dir}/hydrogen/hydrogenclient.cpp - ${lithium_client_dir}/hydrogen/hydrogencamera.cpp - ${lithium_client_dir}/hydrogen/hydrogenfilterwheel.cpp - ${lithium_client_dir}/hydrogen/hydrogentelescope.cpp - ${lithium_client_dir}/hydrogen/hydrogenfocuser.cpp -) - set(config_module ${lithium_src_dir}/config/configor.cpp ) set(device_module ${lithium_src_dir}/device/device_manager.cpp - ${lithium_src_dir}/device/indidevice_manager.cpp + ${lithium_src_dir}/device/hydrogen_manager.cpp ${lithium_src_dir}/device/device_utils.cpp - ${lithium_src_dir}/device/indi_device.cpp ${lithium_src_dir}/device/hydrogen_device.cpp ) @@ -242,12 +230,12 @@ add_subdirectory(${lithium_module_dir}) # 内置的模组 include_directories(modules) add_subdirectory(modules) -add_subdirectory(modules/hydrogen_client) + # 构建Lithium内核 add_subdirectory(${lithium_src_dir}/core) -add_executable(lithium_server ${api_module} ${config_module} ${module_module} ${device_module} ${task_module} ${server_module} ${script_module} ${Lithium_module}) +add_executable(lithium_server ${config_module} ${module_module} ${device_module} ${task_module} ${server_module} ${script_module} ${Lithium_module}) target_link_directories(lithium_server PUBLIC ${CMAKE_BINARY_DIR}/libs) @@ -258,7 +246,7 @@ target_link_libraries(lithium_server atomstatic) target_link_libraries(lithium_server lithiumcorestatic) target_link_libraries(lithium_server lithiumpluginstatic) -target_link_libraries(lithium_server hydrogencore) +target_link_libraries(lithium_server lithiumcore) target_link_libraries(lithium_server hydrogenclientstatic) CHECK_INCLUDE_FILE(format HAS_STD_FORMAT) diff --git a/locale/lithium.pot b/locale/lithium.pot index 2dd7b049..4bcc195c 100644 --- a/locale/lithium.pot +++ b/locale/lithium.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: astro_air@126.com\n" -"POT-Creation-Date: 2023-12-07 06:05+0000\n" +"POT-Creation-Date: 2023-12-13 20:52+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,109 +17,124 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: /workspaces/Lithium/src/device/device_manager.cpp:162 +#: E:/msys64/home/Qrm/Lithium/src/device/device_manager.cpp:162 +#, c++-format msgid "A device with name {} already exists, please choose a different name" msgstr "" -#: /workspaces/Lithium/src/device/indidevice_manager.cpp:278 +#: E:/msys64/home/Qrm/Lithium/src/device/hydrogen_manager.cpp:278 +#, c++-format msgid "Failed to run command: {}" msgstr "" -#: /workspaces/Lithium/src/App.cpp:220 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:220 msgid "port the server running on" msgstr "" -#: /workspaces/Lithium/src/App.cpp:221 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:221 msgid "host the server running on" msgstr "" -#: /workspaces/Lithium/src/App.cpp:222 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:222 msgid "path to the config file" msgstr "" -#: /workspaces/Lithium/src/App.cpp:223 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:223 msgid "path to the modules directory" msgstr "" -#: /workspaces/Lithium/src/App.cpp:224 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:224 msgid "web panel" msgstr "" -#: /workspaces/Lithium/src/App.cpp:225 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:225 msgid "path to log file" msgstr "" -#: /workspaces/Lithium/src/App.cpp:227 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:227 msgid "Lithium Command Line Interface:" msgstr "" -#: /workspaces/Lithium/src/App.cpp:228 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:228 msgid "End." msgstr "" -#: /workspaces/Lithium/src/App.cpp:236 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:236 #, c-format msgid "Failed to parser command line : %s" msgstr "" -#: /workspaces/Lithium/src/App.cpp:258 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:258 #, c-format msgid "Command line server port : %d" msgstr "" -#: /workspaces/Lithium/src/App.cpp:264 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:264 #, c-format msgid "Set server port to %d" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:84 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:84 +#, c++-format msgid "Failed to load Lithium App , error : {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:120 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:120 +#, c++-format msgid "Get config value: {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:126 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:126 +#, c++-format msgid "Set {} to {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:344 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:344 +#, c++-format msgid "Failed to run chai command : {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:362 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:362 +#, c++-format msgid "Failed to run chai multi command {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:375 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:375 +#, c++-format msgid "Failed to load chaiscript file {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:388 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:388 +#, c++-format msgid "Failed to unload chaiscript file {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:401 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:401 +#, c++-format msgid "Failed to run chai script {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:423 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:423 +#, c++-format msgid "Failed to load module {} in {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:442 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:442 +#, c++-format msgid "Failed to unload module {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:464 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:462 +#, c++-format msgid "Failed to reload module {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:502 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:499 +#, c++-format msgid "Failed to enable module {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:521 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:518 +#, c++-format msgid "Failed to disable module {}" msgstr "" diff --git a/locale/po/en_US.UTF-8/lithium.po b/locale/po/en_US.UTF-8/lithium.po index 9bec94c9..ce645e55 100644 --- a/locale/po/en_US.UTF-8/lithium.po +++ b/locale/po/en_US.UTF-8/lithium.po @@ -1,106 +1,121 @@ -#: /workspaces/Lithium/src/device/device_manager.cpp:162 +#: E:/msys64/home/Qrm/Lithium/src/device/device_manager.cpp:162 +#, c++-format msgid "A device with name {} already exists, please choose a different name" msgstr "" -#: /workspaces/Lithium/src/device/indidevice_manager.cpp:278 +#: E:/msys64/home/Qrm/Lithium/src/device/hydrogen_manager.cpp:278 +#, c++-format msgid "Failed to run command: {}" msgstr "" -#: /workspaces/Lithium/src/App.cpp:220 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:220 msgid "port the server running on" msgstr "" -#: /workspaces/Lithium/src/App.cpp:221 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:221 msgid "host the server running on" msgstr "" -#: /workspaces/Lithium/src/App.cpp:222 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:222 msgid "path to the config file" msgstr "" -#: /workspaces/Lithium/src/App.cpp:223 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:223 msgid "path to the modules directory" msgstr "" -#: /workspaces/Lithium/src/App.cpp:224 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:224 msgid "web panel" msgstr "" -#: /workspaces/Lithium/src/App.cpp:225 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:225 msgid "path to log file" msgstr "" -#: /workspaces/Lithium/src/App.cpp:227 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:227 msgid "Lithium Command Line Interface:" msgstr "" -#: /workspaces/Lithium/src/App.cpp:228 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:228 msgid "End." msgstr "" -#: /workspaces/Lithium/src/App.cpp:236 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:236 #, c-format msgid "Failed to parser command line : %s" msgstr "" -#: /workspaces/Lithium/src/App.cpp:258 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:258 #, c-format msgid "Command line server port : %d" msgstr "" -#: /workspaces/Lithium/src/App.cpp:264 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:264 #, c-format msgid "Set server port to %d" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:84 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:84 +#, c++-format msgid "Failed to load Lithium App , error : {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:120 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:120 +#, c++-format msgid "Get config value: {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:126 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:126 +#, c++-format msgid "Set {} to {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:344 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:344 +#, c++-format msgid "Failed to run chai command : {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:362 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:362 +#, c++-format msgid "Failed to run chai multi command {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:375 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:375 +#, c++-format msgid "Failed to load chaiscript file {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:388 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:388 +#, c++-format msgid "Failed to unload chaiscript file {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:401 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:401 +#, c++-format msgid "Failed to run chai script {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:423 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:423 +#, c++-format msgid "Failed to load module {} in {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:442 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:442 +#, c++-format msgid "Failed to unload module {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:464 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:462 +#, c++-format msgid "Failed to reload module {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:502 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:499 +#, c++-format msgid "Failed to enable module {}" msgstr "" -#: /workspaces/Lithium/src/LithiumApp.cpp:521 +#: E:/msys64/home/Qrm/Lithium/src/LithiumApp.cpp:518 +#, c++-format msgid "Failed to disable module {}" msgstr "" diff --git a/modules b/modules index d7f7153d..f1cb5ddf 160000 --- a/modules +++ b/modules @@ -1 +1 @@ -Subproject commit d7f7153d0ad1b699707088dae345162d008d9253 +Subproject commit f1cb5ddf798fd96cbf694b05b5f93a205f8c4e7d diff --git a/src/atom/server/message_bus.hpp b/src/atom/server/message_bus.hpp index 8fc37163..1f7d5f5e 100644 --- a/src/atom/server/message_bus.hpp +++ b/src/atom/server/message_bus.hpp @@ -77,7 +77,7 @@ namespace Lithium }); subscribersLock_.unlock(); - DLOG_F(INFO, "Subscribed to topic: %s", fullTopic.c_str()); + DLOG_F(INFO, "Subscribed to topic: {}", fullTopic); } template @@ -106,7 +106,7 @@ namespace Lithium }), topicSubscribers.end()); - DLOG_F(INFO, "Unsubscribed from topic: %s", fullTopic.c_str()); + DLOG_F(INFO, "Unsubscribed from topic: {}", fullTopic); } subscribersLock_.unlock(); } @@ -121,7 +121,7 @@ namespace Lithium messageQueueLock_.unlock(); messageAvailableFlag_.notify_one(); - DLOG_F(INFO, "Published message to topic: %s", fullTopic.c_str()); + DLOG_F(INFO, "Published message to topic: {}", fullTopic); } template @@ -193,7 +193,7 @@ namespace Lithium } } } catch (const std::bad_any_cast& e) { - LOG_F(ERROR, "Message type mismatch: %s", e.what()); + LOG_F(ERROR, "Message type mismatch: {}", e.what()); } catch (...) { LOG_F(ERROR, "Unknown error occurred during message processing"); } @@ -208,13 +208,13 @@ namespace Lithium } } } catch (const std::bad_any_cast& e) { - LOG_F(ERROR, "Global message type mismatch: %s", e.what()); + LOG_F(ERROR, "Global message type mismatch: {}", e.what()); } catch (...) { LOG_F(ERROR, "Unknown error occurred during global message processing"); } globalSubscribersLock_.unlock(); - DLOG_F(INFO, "Processed message on topic: %s", topic.c_str()); + DLOG_F(INFO, "Processed message on topic: {}", topic); } } })); } @@ -231,7 +231,7 @@ namespace Lithium #endif it->second.join(); processingThreads_.erase(it); - DLOG_F(INFO, "Processing thread for type %s stopped", typeid(T).name()); + DLOG_F(INFO, "Processing thread for type {} stopped", typeid(T).name()); } } diff --git a/src/atom/utils/type.hpp b/src/atom/utils/type.hpp new file mode 100644 index 00000000..aa5d4f58 --- /dev/null +++ b/src/atom/utils/type.hpp @@ -0,0 +1,170 @@ +/* + * type.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-13 + +Description: A simple type wrapper + +**************************************************/ + +#include +#include +#include +#include + +template +class MyType +{ +public: + explicit MyType(const T &value) : m_value(value) {} + + // 加法运算符重载 + template + auto operator+(const MyType &other) const + { + using ResultType = decltype(m_value + other.m_value); + return MyType(m_value + other.m_value); + } + + // 减法运算符重载 + template + auto operator-(const MyType &other) const + { + using ResultType = decltype(m_value - other.m_value); + return MyType(m_value - other.m_value); + } + + // 乘法运算符重载 + template + auto operator*(const MyType &other) const + { + using ResultType = decltype(m_value * other.m_value); + return MyType(m_value * other.m_value); + } + + // 除法运算符重载 + template + auto operator/(const MyType &other) const + { + using ResultType = decltype(m_value / other.m_value); + return MyType(m_value / other.m_value); + } + + // 取模运算符重载 + template + auto operator%(const MyType &other) const + { + using ResultType = decltype(m_value % other.m_value); + return MyType(m_value % other.m_value); + } + + // 等于运算符重载 + template + auto operator==(const MyType &other) const + { + return m_value == other.m_value; + } + + // 不等于运算符重载 + template + auto operator!=(const MyType &other) const + { + return m_value != other.m_value; + } + + // 小于运算符重载 + template + auto operator<(const MyType &other) const + { + return m_value < other.m_value; + } + + // 小于等于运算符重载 + template + auto operator<=(const MyType &other) const + { + return m_value <= other.m_value; + } + + // 大于运算符重载 + template + auto operator>(const MyType &other) const + { + return m_value > other.m_value; + } + + // 大于等于运算符重载 + template + auto operator>=(const MyType &other) const + { + return m_value >= other.m_value; + } + + T m_value; // 成员变量 +}; + +template , typename T = void> +struct TuplePrinter +{ + static void print(const Tuple &t) + { + TuplePrinter::print(t); + std::cout << ", " << std::get(t); + } +}; + +template +struct TuplePrinter::type> +{ + static void print(const Tuple &t) + { + std::cout << std::get<0>(t); + } +}; + +/* +int main() +{ + MyType a(2); + MyType b(3.5); + + auto c = a + b; // MyType(5.5) + auto d = a - b; // MyType(-1.5) + auto e = a * b; // MyType(7.0) + auto f = a / b; // MyType(0.571429) + + std::cout << std::boolalpha << (a == b) << '\n'; // false + std::cout << std::boolalpha << (a != b) << '\n'; // true + std::cout << std::boolalpha << (a < b) << '\n'; // true + std::cout << std::boolalpha << (a <= b) << '\n'; // true + std::cout << std::boolalpha << (a > b) << '\n'; // false + std::cout << std::boolalpha << (a >= b) << '\n'; // false + + auto t = std::make_tuple(1, 2.5, "Hello"); + TuplePrinter::print(t); // 1, 2.5, Hello + + return 0; +} +*/ \ No newline at end of file diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt new file mode 100644 index 00000000..0a63004a --- /dev/null +++ b/src/client/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.13) +project(lithium-driver C CXX) + +find_package(Threads REQUIRED) + +add_subdirectory(ascom) +if(NOT WIN32) +add_subdirectory(hydrogen) +endif() +add_subdirectory(phd2) +add_subdirectory(platesolve) \ No newline at end of file diff --git a/src/client/hydrogen/CMakeLists.txt b/src/client/hydrogen/CMakeLists.txt new file mode 100644 index 00000000..65f30edf --- /dev/null +++ b/src/client/hydrogen/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.13) +project(lithium-driver-hydrogen-client C CXX) + +list(APPEND ${PROJECT_NAME}_SOURCES + hydrogenbasic.cpp + hydrogencamera.cpp + hydrogenclient.cpp + hydrogendome.cpp + hydrogenfilterwheel.cpp + hydrogenfocuser.cpp + hydrogentelescope.cpp +) + +# Headers +list(APPEND ${PROJECT_NAME}_HEADERS + hydrogenbasic.hpp + hydrogencamera.hpp + hydrogenclient.hpp + hydrogendevice.hpp + hydrogendome.hpp + hydrogenfilterwheel.hpp + hydrogenfocuser.hpp + hydrogentelescope.hpp +) + +# Private Headers +list(APPEND ${PROJECT_NAME}_PRIVATE_HEADERS + # TODO +) + +# Build Object Library +add_library(${PROJECT_NAME}_OBJECT OBJECT) +set_property(TARGET ${PROJECT_NAME}_OBJECT PROPERTY POSITION_INDEPENDENT_CODE 1) + +target_sources(${PROJECT_NAME}_OBJECT + PUBLIC + ${${PROJECT_NAME}_HEADERS} + PRIVATE + ${${PROJECT_NAME}_SOURCES} + ${${PROJECT_NAME}_PRIVATE_HEADERS} +) + +target_link_libraries(${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) + +add_library(${PROJECT_NAME}static STATIC) + +target_link_libraries(${PROJECT_NAME}static ${PROJECT_NAME}_OBJECT ${${PROJECT_NAME}_LIBS}) +target_link_libraries(${PROJECT_NAME}static ${CMAKE_THREAD_LIBS_INIT}) +target_include_directories(${PROJECT_NAME}static PUBLIC .) + +set_target_properties(${PROJECT_NAME}static PROPERTIES + VERSION ${CMAKE_HYDROGEN_VERSION_STRING} + SOVERSION ${HYDROGEN_SOVERSION} + OUTPUT_NAME ${PROJECT_NAME} # this same name like shared library - backwards compatibility +) + +install(TARGETS ${PROJECT_NAME}static + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) \ No newline at end of file diff --git a/src/device/device_manager.cpp b/src/device/device_manager.cpp index bad49336..7b858125 100644 --- a/src/device/device_manager.cpp +++ b/src/device/device_manager.cpp @@ -54,8 +54,8 @@ Description: Device Manager #endif #include -#include "indi_device.hpp" -#include "indidevice_manager.hpp" +#include "hydrogen_device.hpp" +#include "hydrogen_manager.hpp" #include "config.h" @@ -114,7 +114,7 @@ namespace Lithium devices.emplace_back(); } - m_indimanager = std::make_shared(); + m_hydrogenmanager = std::make_shared(); } DeviceManager::~DeviceManager() @@ -1230,35 +1230,35 @@ namespace Lithium return DeviceError::None; } - bool DeviceManager::startINDIServer() + bool DeviceManager::startHydrogenServer() { - if (!m_indimanager->is_running()) + if (!m_hydrogenmanager->is_running()) { - m_indimanager->start_server(); + m_hydrogenmanager->start_server(); } return true; } - bool DeviceManager::stopINDIServer() + bool DeviceManager::stopHydrogenServer() { - if (m_indimanager->is_running()) + if (m_hydrogenmanager->is_running()) { - m_indimanager->stop_server(); + m_hydrogenmanager->stop_server(); } return true; } - bool DeviceManager::startINDIDevice() + bool DeviceManager::startHydrogenDevice() { - if (!m_indimanager->is_running()) + if (!m_hydrogenmanager->is_running()) { - LOG_F(ERROR, "INDI server is not started(not by lithium server)"); + LOG_F(ERROR, "Hydrogen server is not started(not by lithium server)"); return false; } return true; } - bool DeviceManager::stopINDIDevice() + bool DeviceManager::stopHydrogenDevice() { return true; } diff --git a/src/device/device_manager.hpp b/src/device/device_manager.hpp index 3e8942d6..81fc21ed 100644 --- a/src/device/device_manager.hpp +++ b/src/device/device_manager.hpp @@ -42,9 +42,8 @@ Description: Device Manager #include "error/error_code.hpp" -class INDIManager; class HydrogenManager; -class INDIDriverCollection; +class HydrogenDriverCollection; class Camera; class Telescope; @@ -286,10 +285,10 @@ namespace Lithium public: - bool startINDIServer(); - bool stopINDIServer(); - bool startINDIDevice(); - bool stopINDIDevice(); + bool startHydrogenServer(); + bool stopHydrogenServer(); + bool startHydrogenDevice(); + bool stopHydrogenDevice(); @@ -318,8 +317,8 @@ namespace Lithium std::shared_ptr m_guider; std::shared_ptr m_solver; - std::shared_ptr m_indimanager; - std::shared_ptr m_indicollection; + std::shared_ptr m_hydrogenmanager; + std::shared_ptr m_hydrogencollection; // For Hydrogen Inside Server public: @@ -327,7 +326,11 @@ namespace Lithium bool startHydrogenDriver(const nlohmann::json &m_params); bool stopHydrogenDriver(const nlohmann::json &m_params); private: +#if __cplusplus >= 202002L std::jthread m_hydrogen_server_thread; +#else + std::thread m_hydrogen_server_thread; +#endif }; } \ No newline at end of file diff --git a/src/device/hydrogen_device.cpp b/src/device/hydrogen_device.cpp index e69de29b..da207def 100644 --- a/src/device/hydrogen_device.cpp +++ b/src/device/hydrogen_device.cpp @@ -0,0 +1,168 @@ +/* + * hydrogen_driver.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-3-29 + +Description: Hydrogen Web Driver + +**************************************************/ + +#include "hydrogen_device.hpp" +#include "device_utils.hpp" + +#include +#include +#include +#include + +#include "atom/type/tinyxml2.h" + +#define LOGURU_USE_FMTLIB 1 +#include "atom/log/loguru.hpp" + +#ifdef _WIN32 +#include +#endif + +namespace fs = std::filesystem; + +HydrogenDeviceContainer::HydrogenDeviceContainer(const std::string &name, const std::string &label, const std::string &version, + const std::string &binary, const std::string &family, + const std::string &skeleton, bool custom) + : name(name), label(label), version(version), binary(binary), + family(family), skeleton(skeleton), custom(custom) {} + +HydrogenDriverCollection::HydrogenDriverCollection(const std::string &path) : path(path) +{ + parseDrivers(); +} + +void HydrogenDriverCollection::parseDrivers() +{ + for (const auto &entry : fs::directory_iterator(path)) + { + const std::string &fname = entry.path().filename().string(); + if (fname.ends_with(".xml") && fname.find("_sk") == std::string::npos) + { + files.push_back(entry.path().string()); + } + } + + for (const std::string &fname : files) + { + tinyxml2::XMLDocument doc; + if (doc.LoadFile(fname.c_str()) != tinyxml2::XML_SUCCESS) + { + LOG_F(ERROR, "Error loading file {}", fname); + continue; + } + + tinyxml2::XMLElement *root = doc.FirstChildElement("root"); + for (tinyxml2::XMLElement *group = root->FirstChildElement("devGroup"); group; group = group->NextSiblingElement("devGroup")) + { + const std::string &family = group->Attribute("group"); + for (tinyxml2::XMLElement *device = group->FirstChildElement("device"); device; device = device->NextSiblingElement("device")) + { + const std::string &label = device->Attribute("label"); + const std::string &skel = device->Attribute("skel"); + const std::string &name = device->FirstChildElement("driver")->Attribute("name"); + const std::string &binary = device->FirstChildElement("driver")->GetText(); + const std::string &version = device->FirstChildElement("version")->GetText(); + + drivers.push_back(std::make_shared(name, label, version, binary, family, skel)); + } + } + } + + // Sort drivers by label + std::sort(drivers.begin(), drivers.end(), [](const std::shared_ptr a, const std::shared_ptr b) + { return a->label < b->label; }); +} + +void HydrogenDriverCollection::parseCustomDrivers(const json &drivers) +{ + for (const auto &custom : drivers) + { + const std::string &name = custom["name"].get(); + const std::string &label = custom["label"].get(); + const std::string &version = custom["version"].get(); + const std::string &binary = custom["exec"].get(); + const std::string &family = custom["family"].get(); + this->drivers.push_back(std::make_shared(name, label, version, binary, family, "", true)); + } +} + +void HydrogenDriverCollection::clearCustomDrivers() +{ + drivers.erase(std::remove_if(drivers.begin(), drivers.end(), [](const std::shared_ptr driver) + { return driver->custom == true; }), + drivers.end()); +} + +std::shared_ptr HydrogenDriverCollection::getByLabel(const std::string &label) +{ + for (auto driver : drivers) + { + if (driver->label == label) + { + return driver; + } + } + return nullptr; +} + +std::shared_ptr HydrogenDriverCollection::getByName(const std::string &name) +{ + for (auto driver : drivers) + { + if (driver->name == name) + { + return driver; + } + } + return nullptr; +} + +std::shared_ptr HydrogenDriverCollection::getByBinary(const std::string &binary) +{ + for (auto driver : drivers) + { + if (driver->binary == binary) + { + return driver; + } + } + return nullptr; +} + +std::map> HydrogenDriverCollection::getFamilies() +{ + std::map> families; + for (const auto driver : drivers) + { + families[driver->family].push_back(driver->label); + } + return families; +} diff --git a/src/device/hydrogen_device.hpp b/src/device/hydrogen_device.hpp index e69de29b..06c3c1ff 100644 --- a/src/device/hydrogen_device.hpp +++ b/src/device/hydrogen_device.hpp @@ -0,0 +1,116 @@ +/* + * hydrogen_driver.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-3-29 + +Description: Hydrogen Web Driver + +**************************************************/ + +#pragma once + +#include +#include +#include +#include + +#include "atom/type/json.hpp" + +using json = nlohmann::json; + +class HydrogenDeviceContainer +{ +public: + std::string name; + std::string label; + std::string version; + std::string binary; + std::string family; + std::string skeleton; + bool custom; + + HydrogenDeviceContainer(const std::string &name, const std::string &label, const std::string &version, + const std::string &binary, const std::string &family, + const std::string &skeleton = "", bool custom = false); +}; + +class HydrogenDriverCollection +{ +public: + /** + * @brief 构造函数 + * @param path Hydrogen驱动程序的路径,默认为"/usr/share/Hydrogen" + */ + HydrogenDriverCollection(const std::string &path = "/usr/share/Hydrogen"); + + /** + * @brief 解析所有Hydrogen驱动程序 + */ + void parseDrivers(); + + /** + * @brief 解析自定义的Hydrogen驱动程序 + * @param drivers JSON格式的自定义驱动程序数据 + */ + void parseCustomDrivers(const json &drivers); + + /** + * @brief 清除自定义的Hydrogen驱动程序 + */ + void clearCustomDrivers(); + + /** + * @brief 根据标签获取Hydrogen设备容器 + * @param label 设备的标签 + * @return 指向HydrogenDeviceContainer的shared_ptr + */ + std::shared_ptr getByLabel(const std::string &label); + + /** + * @brief 根据名称获取Hydrogen设备容器 + * @param name 设备的名称 + * @return 指向HydrogenDeviceContainer的shared_ptr + */ + std::shared_ptr getByName(const std::string &name); + + /** + * @brief 根据二进制文件名获取Hydrogen设备容器 + * @param binary 二进制文件名 + * @return 指向HydrogenDeviceContainer的shared_ptr + */ + std::shared_ptr getByBinary(const std::string &binary); + + /** + * @brief 获取所有Hydrogen设备的家族关系 + * @return 包含家族关系的映射表,键为家族名称,值为设备名称的向量 + */ + std::map> getFamilies(); + +private: + std::string path; ///< Hydrogen驱动程序的路径 + std::vector files; ///< Hydrogen驱动程序文件列表 + std::vector> drivers; ///< Hydrogen驱动程序容器列表 +}; + diff --git a/src/device/indidevice_manager.cpp b/src/device/hydrogen_manager.cpp similarity index 74% rename from src/device/indidevice_manager.cpp rename to src/device/hydrogen_manager.cpp index 3fef47eb..9f160674 100644 --- a/src/device/indidevice_manager.cpp +++ b/src/device/hydrogen_manager.cpp @@ -1,5 +1,5 @@ /* - * indidevice_manager.cpp + * Hydrogendevice_manager.cpp * * Copyright (C) 2023 Max Qian * @@ -25,12 +25,12 @@ E-mail: astro_air@126.com Date: 2023-3-29 -Description: INDI Device Manager +Description: Hydrogen Device Manager **************************************************/ -#include "indidevice_manager.hpp" -#include "indi_device.hpp" +#include "hydrogen_manager.hpp" +#include "hydrogen_device.hpp" #include "device_utils.hpp" #include "config.h" @@ -43,7 +43,7 @@ Description: INDI Device Manager #include "atom/log/loguru.hpp" -INDIManager::INDIManager(const std::string &hst, int prt, const std::string &cfg, const std::string &dta, const std::string &fif) +HydrogenManager::HydrogenManager(const std::string &hst, int prt, const std::string &cfg, const std::string &dta, const std::string &fif) { host = hst; port = prt; @@ -53,9 +53,9 @@ INDIManager::INDIManager(const std::string &hst, int prt, const std::string &cfg } #ifdef _WIN32 -void INDIManager::start_server() +void HydrogenManager::start_server() { - // If there is an INDI server running, just kill it + // If there is an Hydrogen server running, just kill it if (is_running()) { stop_server(); @@ -64,7 +64,7 @@ void INDIManager::start_server() DeleteFileA(fifo_path.c_str()); CreateNamedPipeA(fifo_path.c_str(), PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE | PIPE_WAIT, 1, 0, 0, 0, NULL); // Just start the server without driver - std::string cmd = "indiserver -p " + std::to_string(port) + " -m 100 -v -f " + fifo_path + " > C:\\tmp\\indiserver.log 2>&1"; + std::string cmd = "Hydrogenserver -p " + std::to_string(port) + " -m 100 -v -f " + fifo_path + " > C:\\tmp\\Hydrogenserver.log 2>&1"; STARTUPINFOA si; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(si)); @@ -76,12 +76,12 @@ void INDIManager::start_server() } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); - DLOG_F(INFO, "Started INDI server on port {}", port); + DLOG_F(INFO, "Started Hydrogen server on port {}", port); } #else -void INDIManager::start_server() +void HydrogenManager::start_server() { - // If there is a INDI server running, just kill it + // If there is a Hydrogen server running, just kill it if (is_running()) { stop_server(); @@ -95,35 +95,35 @@ void INDIManager::start_server() } res = system(("mkfifo " + fifo_path).c_str()); // Just start the server without driver - std::string cmd = "indiserver -p " + std::to_string(port) + " -m 100 -v -f " + fifo_path + " > /tmp/indiserver.log 2>&1 &"; + std::string cmd = "Hydrogenserver -p " + std::to_string(port) + " -m 100 -v -f " + fifo_path + " > /tmp/Hydrogenserver.log 2>&1 &"; - DLOG_F(INFO, "Started INDI server on port ", port); + DLOG_F(INFO, "Started Hydrogen server on port ", port); res = system(cmd.c_str()); } #endif -void INDIManager::stop_server() +void HydrogenManager::stop_server() { #ifdef _WIN32 - std::string cmd = "taskkill /f /im indiserver.exe >nul 2>&1"; + std::string cmd = "taskkill /f /im Hydrogenserver.exe >nul 2>&1"; #else - std::string cmd = "killall indiserver >/dev/null 2>&1"; + std::string cmd = "killall Hydrogenserver >/dev/null 2>&1"; #endif int res = system(cmd.c_str()); if (res == 0) { - DLOG_F(INFO, "INDI server terminated successfully"); + DLOG_F(INFO, "Hydrogen server terminated successfully"); } else { - LOG_F(ERROR, "Failed to terminate indiserver, error code is {}", res); + LOG_F(ERROR, "Failed to terminate Hydrogenserver, error code is {}", res); } } #ifdef _WIN32 -bool INDIManager::is_running() +bool HydrogenManager::is_running() { - std::string processName = "indiserver.exe"; + std::string processName = "Hydrogenserver.exe"; bool isRunning = false; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); @@ -149,10 +149,10 @@ bool INDIManager::is_running() return isRunning; } #else -bool INDIManager::is_running() +bool HydrogenManager::is_running() { std::string output = ""; - FILE *pipe = popen("ps -ef | grep indiserver | grep -v grep | wc -l", "r"); + FILE *pipe = popen("ps -ef | grep Hydrogenserver | grep -v grep | wc -l", "r"); if (!pipe) return false; char buffer[128]; @@ -166,7 +166,7 @@ bool INDIManager::is_running() } #endif -void INDIManager::start_driver(std::shared_ptr driver) +void HydrogenManager::start_driver(std::shared_ptr driver) { std::string cmd = "start " + driver->binary; @@ -207,7 +207,7 @@ void INDIManager::start_driver(std::shared_ptr driver) running_drivers.emplace(driver->label, driver); } -void INDIManager::stop_driver(std::shared_ptr driver) +void HydrogenManager::stop_driver(std::shared_ptr driver) { std::string cmd = "stop " + driver->binary; if (driver->binary.find("@") == std::string::npos) @@ -245,18 +245,18 @@ void INDIManager::stop_driver(std::shared_ptr driver) } #ifdef _WIN32 -void INDIManager::set_prop(const std::string &dev, const std::string &prop, const std::string &element, const std::string &value) +void HydrogenManager::set_prop(const std::string &dev, const std::string &prop, const std::string &element, const std::string &value) { std::stringstream ss; - ss << "indi_setprop " << dev << "." << prop << "." << element << "=" << value; + ss << "Hydrogen_setprop " << dev << "." << prop << "." << element << "=" << value; std::string cmd = ss.str(); execute_command(cmd); } -std::string INDIManager::get_prop(const std::string &dev, const std::string &prop, const std::string &element) +std::string HydrogenManager::get_prop(const std::string &dev, const std::string &prop, const std::string &element) { std::stringstream ss; - ss << "indi_getprop " << dev << "." << prop << "." << element; + ss << "Hydrogen_getprop " << dev << "." << prop << "." << element; std::string cmd = ss.str(); std::string output = execute_command(cmd); size_t equalsPos = output.find('='); @@ -267,10 +267,10 @@ std::string INDIManager::get_prop(const std::string &dev, const std::string &pro return ""; } #else -void INDIManager::set_prop(const std::string &dev, const std::string &prop, const std::string &element, const std::string &value) +void HydrogenManager::set_prop(const std::string &dev, const std::string &prop, const std::string &element, const std::string &value) { std::stringstream ss; - ss << "indi_setprop " << dev << "." << prop << "." << element << "=" << value; + ss << "Hydrogen_setprop " << dev << "." << prop << "." << element << "=" << value; std::string cmd = ss.str(); int result = system(cmd.c_str()); if (result != 0) @@ -279,10 +279,10 @@ void INDIManager::set_prop(const std::string &dev, const std::string &prop, cons } } -std::string INDIManager::get_prop(const std::string &dev, const std::string &prop, const std::string &element) +std::string HydrogenManager::get_prop(const std::string &dev, const std::string &prop, const std::string &element) { std::stringstream ss; - ss << "indi_getprop " << dev << "." << prop << "." << element; + ss << "Hydrogen_getprop " << dev << "." << prop << "." << element; std::string cmd = ss.str(); std::array buffer; std::string result = ""; @@ -299,20 +299,20 @@ std::string INDIManager::get_prop(const std::string &dev, const std::string &pro } #endif -std::string INDIManager::get_state(const std::string &dev, const std::string &prop) +std::string HydrogenManager::get_state(const std::string &dev, const std::string &prop) { return get_prop(dev, prop, "_STATE"); } -std::map> INDIManager::get_running_drivers() +std::map> HydrogenManager::get_running_drivers() { return running_drivers; } -std::vector> INDIManager::get_devices() +std::vector> HydrogenManager::get_devices() { std::vector> devices; - std::string cmd = "indi_getprop *.CONNECTION.CONNECT"; + std::string cmd = "Hydrogen_getprop *.CONNECTION.CONNECT"; std::array buffer; std::string result = ""; std::unique_ptr pipe(popen(cmd.c_str(), "r"), pclose); diff --git a/src/device/indidevice_manager.hpp b/src/device/hydrogen_manager.hpp similarity index 60% rename from src/device/indidevice_manager.hpp rename to src/device/hydrogen_manager.hpp index 00be8ebb..c913edc6 100644 --- a/src/device/indidevice_manager.hpp +++ b/src/device/hydrogen_manager.hpp @@ -1,5 +1,5 @@ /* - * indidevice_manager.hpp + * Hydrogendevice_manager.hpp * * Copyright (C) 2023 Max Qian * @@ -25,7 +25,7 @@ E-mail: astro_air@126.com Date: 2023-3-29 -Description: INDI Device Manager +Description: Hydrogen Device Manager **************************************************/ @@ -37,48 +37,48 @@ Description: INDI Device Manager #include #include -class INDIDeviceContainer; +class HydrogenDeviceContainer; -class INDIManager +class HydrogenManager { public: /** * @brief 构造函数 - * @param hst INDI服务器的主机名,默认为"localhost" - * @param prt INDI服务器的端口号,默认为7624 - * @param cfg INDI配置文件路径,默认为空字符串 - * @param dta INDI驱动程序路径,默认为"/usr/share/indi" - * @param fif INDI FIFO路径,默认为"/tmp/indiFIFO" + * @param hst Hydrogen服务器的主机名,默认为"localhost" + * @param prt Hydrogen服务器的端口号,默认为7624 + * @param cfg Hydrogen配置文件路径,默认为空字符串 + * @param dta Hydrogen驱动程序路径,默认为"/usr/share/Hydrogen" + * @param fif Hydrogen FIFO路径,默认为"/tmp/HydrogenFIFO" */ - INDIManager(const std::string &hst = "localhost", int prt = 7624, const std::string &cfg = "", const std::string &dta = "/usr/share/indi", const std::string &fif = "/tmp/indiFIFO"); + HydrogenManager(const std::string &hst = "localhost", int prt = 7624, const std::string &cfg = "", const std::string &dta = "/usr/share/Hydrogen", const std::string &fif = "/tmp/HydrogenFIFO"); /** - * @brief 启动INDI服务器 + * @brief 启动Hydrogen服务器 */ void start_server(); /** - * @brief 停止INDI服务器 + * @brief 停止Hydrogen服务器 */ void stop_server(); /** - * @brief 检查INDI服务器是否正在运行 - * @return 如果INDI服务器正在运行,则返回true;否则返回false + * @brief 检查Hydrogen服务器是否正在运行 + * @return 如果Hydrogen服务器正在运行,则返回true;否则返回false */ bool is_running(); /** - * @brief 启动INDI驱动程序 - * @param driver 要启动的INDI驱动程序的INDIDeviceContainer对象 + * @brief 启动Hydrogen驱动程序 + * @param driver 要启动的Hydrogen驱动程序的HydrogenDeviceContainer对象 */ - void start_driver(std::shared_ptr driver); + void start_driver(std::shared_ptr driver); /** - * @brief 停止INDI驱动程序 - * @param driver 要停止的INDI驱动程序的INDIDeviceContainer对象 + * @brief 停止Hydrogen驱动程序 + * @param driver 要停止的Hydrogen驱动程序的HydrogenDeviceContainer对象 */ - void stop_driver(std::shared_ptr driver); + void stop_driver(std::shared_ptr driver); /** * @brief 设置设备属性值 @@ -108,9 +108,9 @@ class INDIManager /** * @brief 获取正在运行的驱动程序列表 - * @return 包含正在运行的驱动程序的映射表,键为驱动程序名称,值为INDIDeviceContainer对象 + * @return 包含正在运行的驱动程序的映射表,键为驱动程序名称,值为HydrogenDeviceContainer对象 */ - std::map> get_running_drivers(); + std::map> get_running_drivers(); /** * @brief 获取设备列表 @@ -119,10 +119,10 @@ class INDIManager static std::vector> get_devices(); private: - std::string host; ///< INDI服务器的主机名 - int port; ///< INDI服务器的端口号 - std::string config_path; ///< INDI配置文件路径 - std::string data_path; ///< INDI驱动程序路径 - std::string fifo_path; ///< INDI FIFO路径 - std::map> running_drivers; ///< 正在运行的驱动程序列表 + std::string host; ///< Hydrogen服务器的主机名 + int port; ///< Hydrogen服务器的端口号 + std::string config_path; ///< Hydrogen配置文件路径 + std::string data_path; ///< Hydrogen驱动程序路径 + std::string fifo_path; ///< Hydrogen FIFO路径 + std::map> running_drivers; ///< 正在运行的驱动程序列表 }; diff --git a/src/device/indi_device.cpp b/src/device/indi_device.cpp deleted file mode 100644 index 46fedb3d..00000000 --- a/src/device/indi_device.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* - * idriver.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-3-29 - -Description: INDI Web Driver - -**************************************************/ - -#include "indi_device.hpp" -#include "device_utils.hpp" - -#include -#include -#include -#include - -#include - -#define LOGURU_USE_FMTLIB 1 -#include "atom/log/loguru.hpp" - -#ifdef _WIN32 -#include -#endif - -namespace fs = std::filesystem; - -INDIDeviceContainer::INDIDeviceContainer(const std::string &name, const std::string &label, const std::string &version, - const std::string &binary, const std::string &family, - const std::string &skeleton, bool custom) - : name(name), label(label), version(version), binary(binary), - family(family), skeleton(skeleton), custom(custom) {} - -INDIDriverCollection::INDIDriverCollection(const std::string &path) : path(path) -{ - parseDrivers(); -} - -void INDIDriverCollection::parseDrivers() -{ - for (const auto &entry : fs::directory_iterator(path)) - { - const std::string &fname = entry.path().filename().string(); - if (fname.ends_with(".xml") && fname.find("_sk") == std::string::npos) - { - files.push_back(entry.path().string()); - } - } - - for (const std::string &fname : files) - { - pugi::xml_document doc; - if (!doc.load_file(fname.c_str())) - { - LOG_F(ERROR, "Error loading file {}", fname); - continue; - } - - pugi::xml_node root = doc.child("root"); - for (pugi::xml_node group = root.child("devGroup"); group; group = group.next_sibling("devGroup")) - { - const std::string &family = group.attribute("group").as_string(); - for (pugi::xml_node device = group.child("device"); device; device = device.next_sibling("device")) - { - const std::string &label = device.attribute("label").as_string(); - const std::string &skel = device.attribute("skel").as_string(); - const std::string &name = device.child("driver").attribute("name").as_string(); - const std::string &binary = device.child("driver").text().as_string(); - const std::string &version = device.child("version").text().as_string("0.0"); - - drivers.push_back(std::make_shared(name, label, version, binary, family, skel)); - } - } - } - - // Sort drivers by label - std::sort(drivers.begin(), drivers.end(), [](const std::shared_ptr a, const std::shared_ptr b) - { return a->label < b->label; }); -} - -void INDIDriverCollection::parseCustomDrivers(const json &drivers) -{ - for (const auto &custom : drivers) - { - const std::string &name = custom["name"].get(); - const std::string &label = custom["label"].get(); - const std::string &version = custom["version"].get(); - const std::string &binary = custom["exec"].get(); - const std::string &family = custom["family"].get(); - this->drivers.push_back(std::make_shared(name, label, version, binary, family, "", true)); - } -} - -void INDIDriverCollection::clearCustomDrivers() -{ - drivers.erase(std::remove_if(drivers.begin(), drivers.end(), [](const std::shared_ptr driver) - { return driver->custom == true; }), - drivers.end()); -} - -std::shared_ptr INDIDriverCollection::getByLabel(const std::string &label) -{ - for (auto driver : drivers) - { - if (driver->label == label) - { - return driver; - } - } - return nullptr; -} - -std::shared_ptr INDIDriverCollection::getByName(const std::string &name) -{ - for (auto driver : drivers) - { - if (driver->name == name) - { - return driver; - } - } - return nullptr; -} - -std::shared_ptr INDIDriverCollection::getByBinary(const std::string &binary) -{ - for (auto driver : drivers) - { - if (driver->binary == binary) - { - return driver; - } - } - return nullptr; -} - -std::map> INDIDriverCollection::getFamilies() -{ - std::map> families; - for (const auto driver : drivers) - { - families[driver->family].push_back(driver->label); - } - return families; -} diff --git a/src/device/indi_device.hpp b/src/device/indi_device.hpp deleted file mode 100644 index b543047b..00000000 --- a/src/device/indi_device.hpp +++ /dev/null @@ -1,116 +0,0 @@ -/* - * idriver.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-3-29 - -Description: INDI Web Driver - -**************************************************/ - -#pragma once - -#include -#include -#include -#include - -#include "atom/type/json.hpp" - -using json = nlohmann::json; - -class INDIDeviceContainer -{ -public: - std::string name; - std::string label; - std::string version; - std::string binary; - std::string family; - std::string skeleton; - bool custom; - - INDIDeviceContainer(const std::string &name, const std::string &label, const std::string &version, - const std::string &binary, const std::string &family, - const std::string &skeleton = "", bool custom = false); -}; - -class INDIDriverCollection -{ -public: - /** - * @brief 构造函数 - * @param path INDI驱动程序的路径,默认为"/usr/share/indi" - */ - INDIDriverCollection(const std::string &path = "/usr/share/indi"); - - /** - * @brief 解析所有INDI驱动程序 - */ - void parseDrivers(); - - /** - * @brief 解析自定义的INDI驱动程序 - * @param drivers JSON格式的自定义驱动程序数据 - */ - void parseCustomDrivers(const json &drivers); - - /** - * @brief 清除自定义的INDI驱动程序 - */ - void clearCustomDrivers(); - - /** - * @brief 根据标签获取INDI设备容器 - * @param label 设备的标签 - * @return 指向INDIDeviceContainer的shared_ptr - */ - std::shared_ptr getByLabel(const std::string &label); - - /** - * @brief 根据名称获取INDI设备容器 - * @param name 设备的名称 - * @return 指向INDIDeviceContainer的shared_ptr - */ - std::shared_ptr getByName(const std::string &name); - - /** - * @brief 根据二进制文件名获取INDI设备容器 - * @param binary 二进制文件名 - * @return 指向INDIDeviceContainer的shared_ptr - */ - std::shared_ptr getByBinary(const std::string &binary); - - /** - * @brief 获取所有INDI设备的家族关系 - * @return 包含家族关系的映射表,键为家族名称,值为设备名称的向量 - */ - std::map> getFamilies(); - -private: - std::string path; ///< INDI驱动程序的路径 - std::vector files; ///< INDI驱动程序文件列表 - std::vector> drivers; ///< INDI驱动程序容器列表 -}; - diff --git a/src/websocket/template/function.hpp b/src/websocket/template/function.hpp index 394ae34e..eef13af7 100644 --- a/src/websocket/template/function.hpp +++ b/src/websocket/template/function.hpp @@ -5,13 +5,17 @@ try \ { -#define FUNCTION_END \ - } \ - catch (const json::exception &e) \ - { \ - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); \ - } \ - catch (const std::exception &e) \ - { \ - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); \ - } \ No newline at end of file +#define FUNCTION_END \ + } \ + catch (const json::exception &e) \ + { \ + RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); \ + } \ + catch (const std::exception &e) \ + { \ + RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); \ + } \ + catch (...) \ + { \ + RESPONSE_EXCEPTION(res, ServerError::UnknownError, "Unknown Error"); \ + }\ diff --git a/tests/atom/messagebus.cpp b/tests/atom/messagebus.cpp new file mode 100644 index 00000000..cf77bef1 --- /dev/null +++ b/tests/atom/messagebus.cpp @@ -0,0 +1,186 @@ +#include +#include "../../src/atom/server/message_bus.hpp" + +using namespace Lithium; + +class MessageBusTest : public ::testing::Test +{ +protected: + void SetUp() override + { + // Test setup code + } + + void TearDown() override + { + // Test teardown code + } + + // Helper functions +}; + +TEST_F(MessageBusTest, SubscribeToTopic) +{ + MessageBus messageBus; + std::string topic = "test_topic"; + std::function callback = [](int message) + { + // Test callback code + }; + int priority = 0; + std::string namespace_ = ""; + + messageBus.Subscribe(topic, callback, priority, namespace_); +} + +TEST_F(MessageBusTest, UnsubscribeFromTopic) +{ + MessageBus messageBus; + std::string topic = "test_topic"; + std::function callback = [](int message) + { + // Test callback code + }; + int priority = 0; + std::string namespace_ = ""; + + messageBus.Subscribe(topic, callback, priority, namespace_); + + messageBus.Publish(topic, 123); + + messageBus.Unsubscribe(topic, callback, namespace_); +} + +TEST_F(MessageBusTest, PublishMessageToTopic) +{ + MessageBus messageBus; + std::string topic = "test_topic"; + std::function callback = [](int message) + { + // Test callback code + }; + int priority = 0; + std::string namespace_ = ""; + + messageBus.Subscribe(topic, callback, priority, namespace_); + + int message = 123; + messageBus.Publish(topic, message); + + // Test if the published message is received by the callback + // ASSERT_XXXX +} + +TEST_F(MessageBusTest, GlobalSubscribe) +{ + MessageBus messageBus; + std::function callback = [](int message) + { + // Test callback code + }; + int priority = 0; + + messageBus.GlobalSubscribe(callback); +} + +TEST_F(MessageBusTest, GlobalUnsubscribe) +{ + MessageBus messageBus; + std::function callback = [](int message) + { + // Test callback code + }; + int priority = 0; + + messageBus.GlobalSubscribe(callback); + + messageBus.GlobalUnsubscribe(callback); +} + +TEST_F(MessageBusTest, StartProcessingThread) +{ + MessageBus messageBus; + std::type_index typeIndex = typeid(int); +#if __cplusplus >= 202002L + std::thread thread = std::thread([&]() + { messageBus.StartProcessingThread(); }); + thread.detach(); +#else + std::thread thread([&]() + { messageBus.StartProcessingThread(); }); + thread.detach(); +#endif + + // Test if the processing thread is started successfully + // ASSERT_XXXX +} + +TEST_F(MessageBusTest, StopProcessingThread) +{ + MessageBus messageBus; + std::type_index typeIndex = typeid(int); + std::thread thread; +#if __cplusplus >= 202002L + std::thread::id tid = std::this_thread::get_id(); + thread = std::thread([&]() + { + messageBus.StartProcessingThread(); + std::this_thread::sleep_for(std::chrono::seconds(1)); + messageBus.StopProcessingThread(); }); + thread.detach(); + while (std::this_thread::get_id() != tid) + { + std::this_thread::yield(); + } +#else + std::thread::id tid = std::this_thread::get_id(); + thread = std::thread([&]() + { + messageBus.StartProcessingThread(); + std::this_thread::sleep_for(std::chrono::seconds(1)); + messageBus.StopProcessingThread(); }); + thread.detach(); + while (std::this_thread::get_id() != tid) + { + std::this_thread::yield(); + } +#endif + + // Test if the processing thread is stopped successfully + // ASSERT_XXXX +} + +TEST_F(MessageBusTest, StopAllProcessingThreads) +{ + MessageBus messageBus; + std::type_index typeIndex = typeid(int); + std::thread thread; +#if __cplusplus >= 202002L + std::thread::id tid = std::this_thread::get_id(); + thread = std::thread([&]() + { + messageBus.StartProcessingThread(); + std::this_thread::sleep_for(std::chrono::seconds(1)); + messageBus.StopAllProcessingThreads(); }); + thread.detach(); + while (std::this_thread::get_id() != tid) + { + std::this_thread::yield(); + } +#else + std::thread::id tid = std::this_thread::get_id(); + thread = std::thread([&]() + { + messageBus.StartProcessingThread(); + std::this_thread::sleep_for(std::chrono::seconds(1)); + messageBus.StopAllProcessingThreads(); }); + thread.detach(); + while (std::this_thread::get_id() != tid) + { + std::this_thread::yield(); + } +#endif + + // Test if all processing threads are stopped successfully + // ASSERT_XXXX +} \ No newline at end of file From 671b30a8ca26c6a7b883037642ef5a56ad0b93e2 Mon Sep 17 00:00:00 2001 From: Max Qian Date: Sat, 16 Dec 2023 11:28:01 +0800 Subject: [PATCH 7/9] =?UTF-8?q?update=20not=20finished=20=E8=BF=98?= =?UTF-8?q?=E6=9C=89=E5=BE=88=E5=A4=9A=E5=9F=BA=E7=A1=80=E7=9A=84=E4=B8=9C?= =?UTF-8?q?=E8=A5=BF=E9=9C=80=E8=A6=81=E5=AE=8C=E6=88=90=EF=BC=8C=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E7=9A=84=E6=9E=B6=E6=9E=84=E8=BF=98=E6=98=AF?= =?UTF-8?q?=E5=BE=88=E6=B7=B7=E4=B9=B1=EF=BC=8C=E6=95=B4=E4=B8=AA=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=9A=84=E5=90=8C=E6=AD=A5=E6=98=AF=E4=B8=AA=E5=BE=88?= =?UTF-8?q?=E5=A4=A7=E7=9A=84=E9=BA=BB=E7=83=A6=EF=BC=8C=E8=80=8C=E4=B8=94?= =?UTF-8?q?=E6=A8=A1=E7=BB=84=E7=9A=84=E6=B3=A8=E5=85=A5=E7=A9=B6=E7=AB=9F?= =?UTF-8?q?=E8=A6=81=E6=80=8E=E4=B9=88=E6=A0=B7=E6=89=8D=E8=83=BD=E6=96=B9?= =?UTF-8?q?=E4=BE=BF=E7=9A=84=E8=B0=83=E7=94=A8=EF=BC=9F=20=E5=93=8E?= =?UTF-8?q?=EF=BC=8C=E8=BF=99=E9=83=BD=E8=A6=81=E8=8A=B1=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E5=8E=BB=E5=AD=A6=E4=B9=A0=EF=BC=8C=E6=88=91=E5=85=88=E6=8A=8A?= =?UTF-8?q?=E6=9C=80=E5=9F=BA=E7=A1=80=E7=9A=84=E6=90=9E=E5=A5=BD=E5=86=8D?= =?UTF-8?q?=E8=AF=B4=E5=90=A7=E3=80=82=E3=80=82=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 28 +- config.h.in | 8 +- locale/lithium.pot | 26 +- locale/po/en_US.UTF-8/lithium.po | 24 +- src/App.cpp | 2 + src/AppComponent.hpp | 30 +- src/ErrorHandler.cpp | 2 +- src/atom/server/message.hpp | 223 + src/atom/server/message_bus.hpp | 46 +- src/atom/server/variables.hpp | 114 +- src/atom/thread/timer.cpp | 209 + src/atom/thread/timer.hpp | 145 + src/atom/utils/math.cpp | 255 + src/controller/AsyncWebSocketController.hpp | 49 +- src/device/ascom_manager.cpp | 0 src/device/ascom_manager.hpp | 0 src/device/hydrogen_manager.cpp | 21 +- src/device/hydrogen_manager.hpp | 12 +- src/plugin/plugin_loader.cpp | 5 + src/plugin/plugin_loader.hpp | 12 + src/websocket/WebSocketServer.cpp | 423 - src/websocket/WebSocketServer.hpp | 541 - src/websocket/WsBasicComponent.hpp | 24 - src/websocket/WsDeviceComponent.cpp | 308 - src/websocket/WsHub.cpp | 37 + src/websocket/WsHub.hpp | 49 + src/websocket/WsInstance.cpp | 150 + src/websocket/WsInstance.hpp | 105 + src/websocket/WsProcessComponent.cpp | 161 - src/websocket/WsScriptComponent.cpp | 116 - src/websocket/WsServer.cpp | 70 + src/websocket/WsServer.hpp | 119 + src/websocket/WsTaskComponent.cpp | 305 - .../components/WsDeviceComponent.cpp | 0 .../components/WsDeviceComponent.hpp | 0 .../components/WsPluginComponent.cpp | 0 .../components/WsPluginComponent.hpp | 0 .../components/WsScriptComponent.cpp | 0 .../components/WsScriptComponent.hpp | 0 src/websocket/components/WsTaskComponent.cpp | 0 src/websocket/components/WsTaskComponent.hpp | 0 src/websocket/device/WsCameraInstance.cpp | 310 - src/websocket/device/WsCameraInstance.hpp | 106 - src/websocket/device/WsDeviceHub.cpp | 62 - src/websocket/device/WsDeviceHub.hpp | 82 - src/websocket/device/WsDeviceInstance.cpp | 314 - src/websocket/device/WsDeviceInstance.hpp | 304 - src/websocket/device/WsDeviceServer.cpp | 110 - src/websocket/device/WsDeviceServer.hpp | 113 - src/websocket/device/WsFilterInstance.cpp | 57 - src/websocket/device/WsFilterInstance.hpp | 67 - src/websocket/device/WsFocuserInstance.cpp | 57 - src/websocket/device/WsFocuserInstance.hpp | 68 - src/websocket/device/WsTelescopeInstance.cpp | 88 - src/websocket/device/WsTelescopeInstance.hpp | 69 - src/websocket/plugin/WsPluginHub.cpp | 32 - src/websocket/plugin/WsPluginHub.hpp | 52 - src/websocket/plugin/WsPluginInstance.cpp | 106 - src/websocket/plugin/WsPluginInstance.hpp | 114 - src/websocket/plugin/WsPluginServer.cpp | 46 - src/websocket/plugin/WsPluginServer.hpp | 60 - websrc/rubidium-web/package-lock.json | 14195 ++++++++++++++++ websrc/rubidium-web/package.json | 13 + 63 files changed, 15808 insertions(+), 4236 deletions(-) create mode 100644 src/atom/server/message.hpp create mode 100644 src/atom/thread/timer.cpp create mode 100644 src/atom/thread/timer.hpp create mode 100644 src/device/ascom_manager.cpp create mode 100644 src/device/ascom_manager.hpp delete mode 100644 src/websocket/WebSocketServer.cpp delete mode 100644 src/websocket/WebSocketServer.hpp delete mode 100644 src/websocket/WsBasicComponent.hpp delete mode 100644 src/websocket/WsDeviceComponent.cpp create mode 100644 src/websocket/WsHub.cpp create mode 100644 src/websocket/WsHub.hpp create mode 100644 src/websocket/WsInstance.cpp create mode 100644 src/websocket/WsInstance.hpp delete mode 100644 src/websocket/WsProcessComponent.cpp delete mode 100644 src/websocket/WsScriptComponent.cpp create mode 100644 src/websocket/WsServer.cpp create mode 100644 src/websocket/WsServer.hpp delete mode 100644 src/websocket/WsTaskComponent.cpp create mode 100644 src/websocket/components/WsDeviceComponent.cpp create mode 100644 src/websocket/components/WsDeviceComponent.hpp create mode 100644 src/websocket/components/WsPluginComponent.cpp create mode 100644 src/websocket/components/WsPluginComponent.hpp create mode 100644 src/websocket/components/WsScriptComponent.cpp create mode 100644 src/websocket/components/WsScriptComponent.hpp create mode 100644 src/websocket/components/WsTaskComponent.cpp create mode 100644 src/websocket/components/WsTaskComponent.hpp delete mode 100644 src/websocket/device/WsCameraInstance.cpp delete mode 100644 src/websocket/device/WsCameraInstance.hpp delete mode 100644 src/websocket/device/WsDeviceHub.cpp delete mode 100644 src/websocket/device/WsDeviceHub.hpp delete mode 100644 src/websocket/device/WsDeviceInstance.cpp delete mode 100644 src/websocket/device/WsDeviceInstance.hpp delete mode 100644 src/websocket/device/WsDeviceServer.cpp delete mode 100644 src/websocket/device/WsDeviceServer.hpp delete mode 100644 src/websocket/device/WsFilterInstance.cpp delete mode 100644 src/websocket/device/WsFilterInstance.hpp delete mode 100644 src/websocket/device/WsFocuserInstance.cpp delete mode 100644 src/websocket/device/WsFocuserInstance.hpp delete mode 100644 src/websocket/device/WsTelescopeInstance.cpp delete mode 100644 src/websocket/device/WsTelescopeInstance.hpp delete mode 100644 src/websocket/plugin/WsPluginHub.cpp delete mode 100644 src/websocket/plugin/WsPluginHub.hpp delete mode 100644 src/websocket/plugin/WsPluginInstance.cpp delete mode 100644 src/websocket/plugin/WsPluginInstance.hpp delete mode 100644 src/websocket/plugin/WsPluginServer.cpp delete mode 100644 src/websocket/plugin/WsPluginServer.hpp create mode 100644 websrc/rubidium-web/package-lock.json create mode 100644 websrc/rubidium-web/package.json diff --git a/CMakeLists.txt b/CMakeLists.txt index c5eebb48..00ef67c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,24 +131,11 @@ set(module_module ) set(server_module - ${lithium_src_dir}/websocket/WebSocketServer.cpp - ${lithium_src_dir}/websocket/WsDeviceComponent.cpp - ${lithium_src_dir}/websocket/WsProcessComponent.cpp - ${lithium_src_dir}/websocket/WsScriptComponent.cpp - ${lithium_src_dir}/websocket/WsTaskComponent.cpp - - ${lithium_src_dir}/websocket/device/WsDeviceHub.cpp - ${lithium_src_dir}/websocket/device/WsDeviceInstance.cpp - ${lithium_src_dir}/websocket/device/WsDeviceServer.cpp - - ${lithium_src_dir}/websocket/device/WsCameraInstance.cpp - ${lithium_src_dir}/websocket/device/WsTelescopeInstance.cpp - ${lithium_src_dir}/websocket/device/WsFocuserInstance.cpp - ${lithium_src_dir}/websocket/device/WsFilterInstance.cpp - - ${lithium_src_dir}/websocket/plugin/WsPluginHub.cpp - ${lithium_src_dir}/websocket/plugin/WsPluginInstance.cpp - ${lithium_src_dir}/websocket/plugin/WsPluginServer.cpp + ${lithium_src_dir}/websocket/WsServer.cpp + ${lithium_src_dir}/websocket/WsHub.cpp + ${lithium_src_dir}/websocket/WsInstance.cpp + ${lithium_src_dir}/websocket/components/WsDeviceComponent.cpp + ${lithium_src_dir}/websocket/components/WsTaskComponent.cpp ) set(script_module @@ -246,8 +233,10 @@ target_link_libraries(lithium_server atomstatic) target_link_libraries(lithium_server lithiumcorestatic) target_link_libraries(lithium_server lithiumpluginstatic) -target_link_libraries(lithium_server lithiumcore) +target_link_libraries(lithium_server lithiumcorestatic) +if(NOT WIN32) target_link_libraries(lithium_server hydrogenclientstatic) +endif() CHECK_INCLUDE_FILE(format HAS_STD_FORMAT) if(NOT HAS_STD_FORMAT) @@ -264,6 +253,7 @@ target_link_libraries(lithium_server libzip::zip) target_link_libraries(lithium_server sqlite3) target_link_libraries(lithium_server cpp_httplib) target_link_libraries(lithium_server backward) +target_link_libraries(lithium_server tinyxml2) target_link_libraries(lithium_server ${Libintl_LIBRARY}) target_include_directories(lithium_server PUBLIC ${Libintl_INCLUDE_DIRS}) diff --git a/config.h.in b/config.h.in index 71a9dac0..a6e6b5c6 100644 --- a/config.h.in +++ b/config.h.in @@ -3,11 +3,17 @@ #cmakedefine01 ENABLE_FASTHASH #cmakedefine01 ENABLE_DEBUG +#ifdef ENABLE_GETTEXT // I18n #include #include #include - #define _(STRING) gettext(STRING) #define N_(STRING) STRING #define P_(SINGULAR, PLURAL, N) ngettext(SINGULAR, PLURAL, N) +#else +#define _(STRING) (STRING) +#define N_(STRING) (STRING) +#define P_(SINGULAR, PLURAL, N) (N > 1 ? PLURAL : SINGULAR) +#endif + diff --git a/locale/lithium.pot b/locale/lithium.pot index 4bcc195c..9bcca359 100644 --- a/locale/lithium.pot +++ b/locale/lithium.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: astro_air@126.com\n" -"POT-Creation-Date: 2023-12-13 20:52+0800\n" +"POT-Creation-Date: 2023-12-15 18:06+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -22,54 +22,54 @@ msgstr "" msgid "A device with name {} already exists, please choose a different name" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/device/hydrogen_manager.cpp:278 +#: E:/msys64/home/Qrm/Lithium/src/device/hydrogen_manager.cpp:281 #, c++-format msgid "Failed to run command: {}" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:220 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:222 msgid "port the server running on" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:221 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:223 msgid "host the server running on" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:222 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:224 msgid "path to the config file" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:223 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:225 msgid "path to the modules directory" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:224 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:226 msgid "web panel" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:225 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:227 msgid "path to log file" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:227 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:229 msgid "Lithium Command Line Interface:" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:228 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:230 msgid "End." msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:236 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:238 #, c-format msgid "Failed to parser command line : %s" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:258 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:260 #, c-format msgid "Command line server port : %d" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:264 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:266 #, c-format msgid "Set server port to %d" msgstr "" diff --git a/locale/po/en_US.UTF-8/lithium.po b/locale/po/en_US.UTF-8/lithium.po index ce645e55..aa7ad4be 100644 --- a/locale/po/en_US.UTF-8/lithium.po +++ b/locale/po/en_US.UTF-8/lithium.po @@ -3,54 +3,54 @@ msgid "A device with name {} already exists, please choose a different name" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/device/hydrogen_manager.cpp:278 +#: E:/msys64/home/Qrm/Lithium/src/device/hydrogen_manager.cpp:281 #, c++-format msgid "Failed to run command: {}" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:220 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:222 msgid "port the server running on" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:221 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:223 msgid "host the server running on" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:222 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:224 msgid "path to the config file" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:223 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:225 msgid "path to the modules directory" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:224 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:226 msgid "web panel" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:225 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:227 msgid "path to log file" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:227 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:229 msgid "Lithium Command Line Interface:" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:228 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:230 msgid "End." msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:236 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:238 #, c-format msgid "Failed to parser command line : %s" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:258 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:260 #, c-format msgid "Command line server port : %d" msgstr "" -#: E:/msys64/home/Qrm/Lithium/src/App.cpp:264 +#: E:/msys64/home/Qrm/Lithium/src/App.cpp:266 #, c-format msgid "Set server port to %d" msgstr "" diff --git a/src/App.cpp b/src/App.cpp index 2af7dae8..39807d79 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -210,10 +210,12 @@ void registerInterruptHandler() */ int main(int argc, char *argv[]) { +#if ENABLE_GETTEXT bindtextdomain("lithium", "locale"); /* Only write the following 2 lines if creating an executable */ setlocale(LC_ALL, ""); textdomain("lithium"); +#endif argparse::ArgumentParser program("Lithium Server"); diff --git a/src/AppComponent.hpp b/src/AppComponent.hpp index 52f20116..cc40feea 100644 --- a/src/AppComponent.hpp +++ b/src/AppComponent.hpp @@ -34,9 +34,7 @@ Description: App Components #include "config.h" -#include "websocket/WebSocketServer.hpp" -#include "websocket/device/WsDeviceServer.hpp" -#include "websocket/plugin/WsPluginServer.hpp" +#include "websocket/WsServer.hpp" #include "ErrorHandler.hpp" @@ -207,31 +205,7 @@ class AppComponent #else auto connectionHandler = oatpp::websocket::ConnectionHandler::createShared(); #endif - connectionHandler->setSocketInstanceListener(std::make_shared()); - return connectionHandler; }()); - - OATPP_CREATE_COMPONENT(std::shared_ptr, websocketDeviceConnectionHandler) - ("websocket-device", [] - { -#if ENABLE_ASYNC - OATPP_COMPONENT(std::shared_ptr, executor); - auto connectionHandler = oatpp::websocket::AsyncConnectionHandler::createShared(executor); -#else - auto connectionHandler = oatpp::websocket::ConnectionHandler::createShared(); -#endif - connectionHandler->setSocketInstanceListener(std::make_shared()); - return connectionHandler; }()); - - OATPP_CREATE_COMPONENT(std::shared_ptr, websocketScriptConnectionHandler) - ("websocket-script", [] - { -#if ENABLE_ASYNC - OATPP_COMPONENT(std::shared_ptr, executor); - auto connectionHandler = oatpp::websocket::AsyncConnectionHandler::createShared(executor); -#else - auto connectionHandler = oatpp::websocket::ConnectionHandler::createShared(); -#endif - connectionHandler->setSocketInstanceListener(std::make_shared()); + connectionHandler->setSocketInstanceListener(std::make_shared()); return connectionHandler; }()); }; diff --git a/src/ErrorHandler.cpp b/src/ErrorHandler.cpp index 79bdddd8..e3486189 100644 --- a/src/ErrorHandler.cpp +++ b/src/ErrorHandler.cpp @@ -55,7 +55,7 @@ ErrorHandler::handleError(const Status &status, const oatpp::String &message, co std::stringstream htmlStream; htmlStream << "404

        :(

        Lithium Server seemed to have crashed.We're sorry for the\n"; htmlStream << "inconvenience. The server will automatically restart in a few minutes.\n"; - htmlStream << fmt::format("We're just collecting some error info.

        0%complete

        QR Code

        For more information about this issue and possible fixes,visit
        https:

        If you call a support person,give them this info:
        Stop Code:{}Message:{}
        ", error->code, error->message); + //htmlStream << fmt::format("We're just collecting some error info.

        0%complete

        QR Code

        For more information about this issue and possible fixes,visit
        https:

        If you call a support person,give them this info:
        Stop Code:{}Message:{}
        ", error->code, error->message); auto response = ResponseFactory::createResponse(Status::CODE_404, htmlStream.str()); return response; } diff --git a/src/atom/server/message.hpp b/src/atom/server/message.hpp new file mode 100644 index 00000000..c7f811e0 --- /dev/null +++ b/src/atom/server/message.hpp @@ -0,0 +1,223 @@ +#include +#include +#include +#include +#include + +// 基类 Message +class Message +{ +public: + virtual ~Message() = default; + + // 消息类型枚举 + enum class Type + { + kText, + kNumber, + kBoolean, + kStructuredData, + kCustom1, + kCustom2, + kMaxType + }; + + Type type() const { return type_; } + +protected: + explicit Message(Type t) : type_(t) {} + +private: + Type type_; + + std::string_view timestamp; + std::string_view sender; + std::string_view target; +}; + +// TextMessage 类 +class TextMessage : public Message +{ +public: + explicit TextMessage(std::string text) : Message(Type::kText), text_(std::move(text)) {} + const std::string &text() const { return text_; } + +private: + std::string text_; +}; + +// NumberMessage 类 +class NumberMessage : public Message +{ +public: + explicit NumberMessage(double number) : Message(Type::kNumber), number_(number) {} + double number() const { return number_; } + +private: + double number_; +}; + +// StructuredDataMessage 类 +class StructuredDataMessage : public Message +{ +public: + explicit StructuredDataMessage(std::vector data) : Message(Type::kStructuredData), data_(std::move(data)) {} + const std::vector &data() const { return data_; } + +private: + std::vector data_; +}; + +// CustomMessage1 类 +class CustomMessage1 : public Message +{ +public: + explicit CustomMessage1(std::string data) : Message(Type::kCustom1), data_(std::move(data)) {} + const std::string &data() const { return data_; } + +private: + std::string data_; +}; + +// CustomMessage2 类 +class CustomMessage2 : public Message +{ +public: + explicit CustomMessage2(int value) : Message(Type::kCustom2), value_(value) {} + int value() const { return value_; } + +private: + int value_; +}; + +// Message 类的工厂函数 +template +std::unique_ptr MakeMessage(Args &&...args) +{ + return std::make_unique(std::forward(args)...); +} + +// VariantMessage 类,使用 std::variant 存储各种消息类型 +class VariantMessage +{ +public: + VariantMessage() : msg_(TextMessage("")) {} + template + void Set(T message) + { + msg_ = std::move(message); + } + + template + const T &Get() const + { + return std::get(msg_); + } + + Message::Type type() const + { + try + { + return static_cast(msg_.index()); + } + catch (const std::bad_variant_access &) + { + return Message::Type::kMaxType; + } + } + +private: + std::variant< + TextMessage, + NumberMessage, + StructuredDataMessage, + CustomMessage1, + CustomMessage2> + msg_; +}; + +int main() +{ + // 使用工厂函数创建不同类型的消息 + auto text_msg = MakeMessage("Hello, world!"); + auto number_msg = MakeMessage(3.14); + auto structured_data_msg = MakeMessage(std::vector{1, 2, 3}); + auto custom_msg1 = MakeMessage("Custom message 1"); + auto custom_msg2 = MakeMessage(42); + + // 将消息存储在 VariantMessage 中 + VariantMessage variant_msg; + variant_msg.Set(std::move(*text_msg)); + + // 通过 type() 函数获取消息类型,并根据类型进行相应操作 + switch (variant_msg.type()) + { + case Message::Type::kText: + std::cout << "Text Message: " << variant_msg.Get().text() << '\n'; + break; + + case Message::Type::kNumber: + std::cout << "Number Message: " << variant_msg.Get().number() << '\n'; + break; + + case Message::Type::kStructuredData: + std::cout << "Structured Data Message: "; + for (const auto &num : variant_msg.Get().data()) + { + std::cout << num << ' '; + } + std::cout << '\n'; + break; + + case Message::Type::kCustom1: + std::cout << "Custom Message 1: " << variant_msg.Get().data() << '\n'; + break; + + case Message::Type::kCustom2: + std::cout << "Custom Message 2: " << variant_msg.Get().value() << '\n'; + break; + + default: + std::cout << "Unknown Message Type\n"; + break; + } + + // 添加更多的消息类型 + variant_msg.Set(std::move(*number_msg)); + variant_msg.Set(std::move(*structured_data_msg)); + + // 通过 type() 函数获取消息类型,并根据类型进行相应操作 + switch (variant_msg.type()) + { + case Message::Type::kText: + std::cout << "Text Message: " << variant_msg.Get().text() << '\n'; + break; + + case Message::Type::kNumber: + std::cout << "Number Message: " << variant_msg.Get().number() << '\n'; + break; + + case Message::Type::kStructuredData: + std::cout << "Structured Data Message: "; + for (const auto &num : variant_msg.Get().data()) + { + std::cout << num << ' '; + } + std::cout << '\n'; + break; + + case Message::Type::kCustom1: + std::cout << "Custom Message 1: " << variant_msg.Get().data() << '\n'; + break; + + case Message::Type::kCustom2: + std::cout << "Custom Message 2: " << variant_msg.Get().value() << '\n'; + break; + + default: + std::cout << "Unknown Message Type\n"; + break; + } + + return 0; +} diff --git a/src/atom/server/message_bus.hpp b/src/atom/server/message_bus.hpp index 1f7d5f5e..85bb09d7 100644 --- a/src/atom/server/message_bus.hpp +++ b/src/atom/server/message_bus.hpp @@ -58,11 +58,55 @@ namespace Lithium class MessageBus { public: + MessageBus() + { + + } + + MessageBus(const int &max_queue_size) + { + maxMessageBusSize_.store(max_queue_size); + } + + ~MessageBus() + { + if (!subscribers_.empty()) + { + subscribers_.clear(); + } + if (!processingThreads_.empty()) + { + for (auto &thread : processingThreads_) + { + if (thread.second.joinable()) + { +#if __cplusplus >= 202002L + thread.second.request_stop(); +#endif + thread.second.join(); + } + } + processingThreads_.clear(); + } + } + // ------------------------------------------------------------------- + // Common methods + // ------------------------------------------------------------------- + static std::shared_ptr createShared() { return std::make_shared(); } + + static std::unique_ptr createUnique() + { + return std::make_unique(); + } public: + // ------------------------------------------------------------------- + // MessageBus methods + // ------------------------------------------------------------------- + template void Subscribe(const std::string &topic, std::function callback, int priority = 0, const std::string &namespace_ = "") { @@ -269,9 +313,9 @@ namespace Lithium #else std::unordered_map processingThreads_; #endif - #endif std::atomic isRunning_{true}; + std::atomic_int maxMessageBusSize_{1000}; std::vector globalSubscribers_; std::mutex globalSubscribersLock_; diff --git a/src/atom/server/variables.hpp b/src/atom/server/variables.hpp index 67086013..e581e1d9 100644 --- a/src/atom/server/variables.hpp +++ b/src/atom/server/variables.hpp @@ -23,9 +23,9 @@ Author: Max Qian E-mail: astro_air@126.com -Date: 2023-11-3 +Date: 2023-12-14 -Description: Variable Registry +Description: Variable Registry 类,用于注册、获取和观察变量值。 **************************************************/ @@ -39,6 +39,7 @@ Description: Variable Registry #include #include #include +#include #include "atom/type/json.hpp" @@ -89,10 +90,13 @@ class VariableRegistry template bool SetVariable(const std::string &name, T &&value) { - std::lock_guard lock(m_mutex); - + std::shared_lock lock(m_sharedMutex); if (auto it = m_variables.find(name); it != m_variables.end()) { + if (auto setter = m_setters.find(name); setter != m_setters.end()) + { + setter->second(std::forward(value)); + } it->second = std::forward(value); NotifyObservers(name, value); return true; @@ -165,9 +169,9 @@ class VariableRegistry { std::lock_guard lock(m_mutex); - if (auto it = m_observers.find(name); it!= m_observers.end()) + if (auto it = m_observers.find(name); it != m_observers.end()) { - for (auto it2 = it->second.begin(); it2!= it->second.end(); ++it2) + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { if (it2->name == observerName) { @@ -193,6 +197,34 @@ class VariableRegistry return true; } + /** + * @brief 添加获取指定名称变量的回调函数。 + * @tparam T 变量类型,需要与注册时相同。 + * @param name 变量名称。 + * @param getter 获取变量值的函数。 + */ + template + void AddGetter(const std::string &name, const std::function &getter) + { + std::lock_guard lock(m_mutex); + + m_getters[name] = getter; + } + + /** + * @brief 添加检测指定名称变量修改的回调函数。 + * @tparam T 变量类型,需要与注册时相同。 + * @param name 变量名称。 + * @param setter 检测变量修改的函数。 + */ + template + void AddSetter(const std::string &name, const std::function &setter) + { + std::lock_guard lock(m_mutex); + + m_setters[name] = setter; + } + private: /** * @brief 所有变量的集合。 @@ -204,47 +236,99 @@ class VariableRegistry */ std::unordered_map> m_observers; + /** + * @brief 获取函数的集合,以变量名称为键。 + */ + std::unordered_map> m_getters; + + /** + * @brief 检测修改函数的集合,以变量名称为键。 + */ + std::unordered_map> m_setters; + /** * @brief 互斥锁,用于保证多线程安全。 */ mutable std::mutex m_mutex; + + mutable std::shared_mutex m_sharedMutex; }; static std::string SerializeVariablesToJson(const VariableRegistry ®istry) { - json j; - + std::string j = "{"; for (const auto entry : registry.GetAll()) { const std::string &name = entry.first; const std::any &value = entry.second; - try { if (value.type() == typeid(int)) { - j[name] = std::any_cast(value); + j += "\"" + name + "\":" + std::to_string(std::any_cast(value)) + ","; } else if (value.type() == typeid(double)) { - j[name] = std::any_cast(value); + j += "\"" + name + "\":" + std::to_string(std::any_cast(value)) + ","; } else if (value.type() == typeid(bool)) { - j[name] = std::any_cast(value); + j += "\"" + name + "\":" + std::to_string(std::any_cast(value)) + ","; } else if (value.type() == typeid(std::string)) { - j[name] = std::any_cast(value); + j += "\"" + name + "\":\"" + std::any_cast(value) + "\","; + } + else if (value.type() == typeid(std::vector)) + { + j += "\"" + name + "\":["; + for (const auto &i : std::any_cast>(value)) + { + j += std::to_string(i) + ","; + } + j.pop_back(); // 删除末尾多余的逗号 + j += "],"; + } + else if (value.type() == typeid(std::vector)) + { + j += "\"" + name + "\":["; + for (const auto &i : std::any_cast>(value)) + { + j += std::to_string(i) + ","; + } + j.pop_back(); + j += "],"; + } + else if (value.type() == typeid(std::vector)) + { + j += "\"" + name + "\":["; + for (const auto &i : std::any_cast>(value)) + { + j += std::to_string(i) + ","; + } + j.pop_back(); + j += "],"; + } + else if (value.type() == typeid(std::vector)) + { + j += "\"" + name + "\":["; + for (const auto &i : std::any_cast>(value)) + { + j += "\"" + i + "\","; + } } } catch (const std::bad_any_cast &) { - // 转换失败,忽略该变量或进行其他处理 } } + if (!registry.GetAll().empty()) + { + j.pop_back(); // 删除末尾多余的逗号 + } - return j.dump(); + j += "}"; + return j; } /* diff --git a/src/atom/thread/timer.cpp b/src/atom/thread/timer.cpp new file mode 100644 index 00000000..56ef7ba4 --- /dev/null +++ b/src/atom/thread/timer.cpp @@ -0,0 +1,209 @@ +/* + * timer.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-14 + +Description: Timer class for C++ + +**************************************************/ + +#include "timer.hpp" + +TimerTask::TimerTask(std::function func, unsigned int delay, int repeatCount, int priority) + : m_func(func), m_delay(delay), m_repeatCount(repeatCount), m_priority(priority) +{ + m_nextExecutionTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(m_delay); +} + +bool TimerTask::operator<(const TimerTask &other) const +{ + if (m_priority != other.m_priority) + { + return m_priority > other.m_priority; + } + else + { + return m_nextExecutionTime > other.m_nextExecutionTime; + } +} + +void TimerTask::run() +{ + try + { + m_func(); + } + catch (const std::exception &e) + { + std::throw_with_nested(std::runtime_error("Failed to run timer task")); + } + if (m_repeatCount > 0) + { + --m_repeatCount; + if (m_repeatCount > 0) + { + m_nextExecutionTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(m_delay); + } + } +} + +std::chrono::steady_clock::time_point TimerTask::getNextExecutionTime() const +{ + return m_nextExecutionTime; +} + +Timer::Timer() + : m_stop(false), m_paused(false) +{ + m_thread = std::thread(&Timer::run, this); +} + +Timer::~Timer() +{ + stop(); + if (m_thread.joinable()) + { + m_thread.join(); + } +} + +void Timer::cancelAllTasks() +{ + std::unique_lock lock(m_mutex); + m_taskQueue = std::priority_queue(); + m_cond.notify_all(); +} + +void Timer::pause() +{ + m_paused = true; +} + +void Timer::resume() +{ + m_paused = false; + m_cond.notify_all(); +} + +void Timer::stop() +{ + m_stop = true; + m_cond.notify_all(); +} + +void Timer::run() +{ + while (!m_stop) + { + std::unique_lock lock(m_mutex); + while (!m_stop && m_paused && m_taskQueue.empty()) + { + m_cond.wait(lock); + } + if (m_stop) + { + break; + } + if (!m_taskQueue.empty()) + { + TimerTask task = m_taskQueue.top(); + if (std::chrono::steady_clock::now() >= task.getNextExecutionTime()) + { + m_taskQueue.pop(); + lock.unlock(); + task.run(); + if (task.m_repeatCount > 0) + { + std::unique_lock lock(m_mutex); + m_taskQueue.emplace(task.m_func, task.m_delay, task.m_repeatCount, task.m_priority); + } + if (m_callback) + { + m_callback(); + } + } + else + { + m_cond.wait_until(lock, task.getNextExecutionTime()); + } + } + } +} + +/* + +// 用法示例 +int main() +{ + Timer timer; + + // 可取消的定时器任务 + std::future future = timer.setTimeout([]() + { std::cout << "Timeout!" << std::endl; }, + 1000); + timer.setTimeout([]() + { std::cout << "This should not be printed!" << std::endl; }, + 2000); + timer.cancelAllTasks(); + + // 任务函数支持返回值 + std::future result = timer.setTimeout([]() + { return 42; }, + 2000); + std::cout << "Result: " << result.get() << std::endl; + + // 定时器线程池 + for (int i = 0; i < 5; ++i) + { + timer.setInterval([i]() + { std::cout << "Interval from thread " << i << "!" << std::endl; }, + 500, 10 - i, i); + } + + // 暂停定时器任务 + timer.pause(); + std::this_thread::sleep_for(std::chrono::seconds(3)); + + // 恢复定时器任务 + timer.resume(); + + timer.setTimeout([]() -> void + { std::cout << "High priority task!" << std::endl; }, + 1000); + timer.setTimeout([]() -> void + { std::cout << "Low priority task!" << std::endl; }, + 1000); + + // 添加回调函数 + timer.setCallback([]() + { std::cout << "Task completed!" << std::endl; }); + + std::this_thread::sleep_for(std::chrono::seconds(10)); + timer.stop(); + + return 0; +} + +*/ diff --git a/src/atom/thread/timer.hpp b/src/atom/thread/timer.hpp new file mode 100644 index 00000000..2d807810 --- /dev/null +++ b/src/atom/thread/timer.hpp @@ -0,0 +1,145 @@ +/* + * timer.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-14 + +Description: Timer class for C++ + +**************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class TimerTask +{ +public: + TimerTask(std::function func, unsigned int delay, int repeatCount, int priority); + + bool operator<(const TimerTask &other) const; + + void run(); + + std::chrono::steady_clock::time_point getNextExecutionTime() const; + + std::function m_func; + unsigned int m_delay; + int m_repeatCount; + int m_priority; + std::chrono::steady_clock::time_point m_nextExecutionTime; +}; + +class Timer +{ +public: + Timer(); + + ~Timer(); + + template + std::future::type> setTimeout(Function &&func, unsigned int delay, Args &&...args); + + template + void setInterval(Function &&func, unsigned int interval, int repeatCount, int priority, Args &&...args); + + void cancelAllTasks(); + + void pause(); + + void resume(); + + void stop(); + + template + void setCallback(Function &&func); + +private: + template + std::future::type> addTask(Function &&func, unsigned int delay, int repeatCount, int priority, Args &&...args); + + void run(); + +private: +#if _cplusplus >= 202203L + std::jthread m_thread; +#else + std::thread m_thread; +#endif + std::priority_queue m_taskQueue; + std::mutex m_mutex; + std::condition_variable m_cond; + std::function m_callback; + bool m_stop; + bool m_paused; +}; + +template +std::future::type> Timer::setTimeout(Function &&func, unsigned int delay, Args &&...args) +{ + using ReturnType = typename std::result_of::type; + auto task = std::make_shared>(std::bind(std::forward(func), std::forward(args)...)); + std::future result = task->get_future(); + std::unique_lock lock(m_mutex); + m_taskQueue.emplace([task]() + { (*task)(); }, + delay, 1, 0); + m_cond.notify_all(); + return result; +} + +template +void Timer::setInterval(Function &&func, unsigned int interval, int repeatCount, int priority, Args &&...args) +{ + addTask(std::forward(func), interval, repeatCount, priority, std::forward(args)...); +} + +template +std::future::type> Timer::addTask(Function &&func, unsigned int delay, int repeatCount, int priority, Args &&...args) +{ + using ReturnType = typename std::result_of::type; + auto task = std::make_shared>(std::bind(std::forward(func), std::forward(args)...)); + std::future result = task->get_future(); + std::unique_lock lock(m_mutex); + m_taskQueue.emplace([task]() + { (*task)(); }, + delay, repeatCount, priority); + m_cond.notify_all(); + return result; +} + +template +void Timer::setCallback(Function &&func) +{ + m_callback = std::forward(func); +} \ No newline at end of file diff --git a/src/atom/utils/math.cpp b/src/atom/utils/math.cpp index a75e6465..34284935 100644 --- a/src/atom/utils/math.cpp +++ b/src/atom/utils/math.cpp @@ -31,6 +31,10 @@ Description: Extra Math Library #include "math.hpp" +#include +#include +#include + namespace Math { /* "+",加法重载部分 */ @@ -298,4 +302,255 @@ namespace Math return output; } + std::vector> convolve2D(const std::vector> &input, const std::vector> &kernel, int numThreads = 1) + { + int inputRows = input.size(); + int inputCols = input[0].size(); + int kernelRows = kernel.size(); + int kernelCols = kernel[0].size(); + + // 将输入矩阵和卷积核矩阵扩展到相同的大小,使用0填充 + std::vector> extendedInput(inputRows + kernelRows - 1, std::vector(inputCols + kernelCols - 1, 0)); + std::vector> extendedKernel(inputRows + kernelRows - 1, std::vector(inputCols + kernelCols - 1, 0)); + + for (int i = 0; i < inputRows; ++i) + { + for (int j = 0; j < inputCols; ++j) + { + extendedInput[i + kernelRows / 2][j + kernelCols / 2] = input[i][j]; + } + } + + for (int i = 0; i < kernelRows; ++i) + { + for (int j = 0; j < kernelCols; ++j) + { + extendedKernel[i][j] = kernel[i][j]; + } + } + + // 计算卷积结果 + std::vector> output(inputRows, std::vector(inputCols, 0)); + + auto computeBlock = [&](int blockStartRow, int blockEndRow) + { + for (int i = blockStartRow; i < blockEndRow; ++i) + { + for (int j = kernelCols / 2; j < inputCols + kernelCols / 2; ++j) + { + double sum = 0; + for (int k = -kernelRows / 2; k <= kernelRows / 2; ++k) + { + for (int l = -kernelCols / 2; l <= kernelCols / 2; ++l) + { + sum += extendedInput[i + k][j + l] * extendedKernel[kernelRows / 2 + k][kernelCols / 2 + l]; + } + } + output[i - kernelRows / 2][j - kernelCols / 2] = sum; + } + } + }; + + if (numThreads == 1) + { + computeBlock(kernelRows / 2, inputRows + kernelRows / 2); + } + else + { + std::vector threads(numThreads); + int blockSize = (inputRows + numThreads - 1) / numThreads; + int blockStartRow = kernelRows / 2; + for (int i = 0; i < numThreads; ++i) + { + int blockEndRow = std::min(blockStartRow + blockSize, inputRows + kernelRows / 2); + threads[i] = std::thread(computeBlock, blockStartRow, blockEndRow); + blockStartRow = blockEndRow; + } + for (auto &thread : threads) + { + thread.join(); + } + } + + return output; + } + + // 二维离散傅里叶变换(2D DFT) + std::vector>> DFT2D(const std::vector> &signal) + { + int M = signal.size(); + int N = signal[0].size(); + std::vector>> X(M, std::vector>(N, {0, 0})); + + for (int u = 0; u < M; ++u) + { + for (int v = 0; v < N; ++v) + { + std::complex sum(0, 0); + for (int m = 0; m < M; ++m) + { + for (int n = 0; n < N; ++n) + { + double theta = 2 * M_PI * (u * m / static_cast(M) + v * n / static_cast(N)); + std::complex w(cos(theta), -sin(theta)); + sum += signal[m][n] * w; + } + } + X[u][v] = sum; + } + } + + return X; + } + + // 二维离散傅里叶逆变换(2D IDFT) + std::vector> IDFT2D(const std::vector>> &spectrum) + { + int M = spectrum.size(); + int N = spectrum[0].size(); + std::vector> x(M, std::vector(N, 0)); + + for (int m = 0; m < M; ++m) + { + for (int n = 0; n < N; ++n) + { + std::complex sum(0, 0); + for (int u = 0; u < M; ++u) + { + for (int v = 0; v < N; ++v) + { + double theta = 2 * M_PI * (u * m / static_cast(M) + v * n / static_cast(N)); + std::complex w(cos(theta), sin(theta)); + sum += spectrum[u][v] * w; + } + } + x[m][n] = real(sum) / (M * N); + } + } + + return x; + } + + // 二维反卷积函数 + std::vector> deconvolve2D(const std::vector> &signal, const std::vector> &kernel) + { + // 获取信号和卷积核的维度 + int M = signal.size(); + int N = signal[0].size(); + int K = kernel.size(); + int L = kernel[0].size(); + + // 将信号和卷积核扩展到相同的大小,使用0填充 + std::vector> extendedSignal(M + K - 1, std::vector(N + L - 1, 0)); + std::vector> extendedKernel(M + K - 1, std::vector(N + L - 1, 0)); + + // 将信号复制到扩展后的信号数组中 + for (int i = 0; i < M; ++i) + { + for (int j = 0; j < N; ++j) + { + extendedSignal[i][j] = signal[i][j]; + } + } + + // 将卷积核复制到扩展后的卷积核数组中 + for (int i = 0; i < K; ++i) + { + for (int j = 0; j < L; ++j) + { + extendedKernel[i][j] = kernel[i][j]; + } + } + + // 计算信号和卷积核的二维DFT + auto X = DFT2D(extendedSignal); + auto H = DFT2D(extendedKernel); + + // 对卷积核的频谱进行修正 + std::vector>> G(M, std::vector>(N, {0, 0})); + double alpha = 0.1; // 防止分母为0 + for (int u = 0; u < M; ++u) + { + for (int v = 0; v < N; ++v) + { + if (std::abs(H[u][v]) > alpha) + { + G[u][v] = std::conj(H[u][v]) / (std::norm(H[u][v]) + alpha); + } + else + { + G[u][v] = std::conj(H[u][v]); + } + } + } + + // 计算反卷积结果 + std::vector>> Y(M, std::vector>(N, {0, 0})); + for (int u = 0; u < M; ++u) + { + for (int v = 0; v < N; ++v) + { + Y[u][v] = G[u][v] * X[u][v]; + } + } + auto y = IDFT2D(Y); + + // 取出结果的前M行、前N列 + std::vector> result(M, std::vector(N, 0)); + for (int i = 0; i < M; ++i) + { + for (int j = 0; j < N; ++j) + { + result[i][j] = y[i][j]; + } + } + + return result; + } + +} + +/* +int main() +{ + int inputRows = 1000; + int inputCols = 1000; + int kernelRows = 3; + int kernelCols = 3; + + std::vector> input(inputRows, std::vector(inputCols, 1)); + std::vector> kernel(kernelRows, std::vector(kernelCols, 1)); + + auto output = convolve2D(input, kernel, 4); + + for (const auto &row : output) + { + for (auto val : row) + { + std::cout << val << " "; + } + std::cout << '\n'; + } + + return 0; } + +int main() + { + std::vector> signal = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}, {13, 14, 15}}; + std::vector> kernel = {{1, 0, 1}, {0, 1, 0}, {1, 0, 1}}; + + auto result = deconvolve2D(signal, kernel); + + for (const auto &row : result) + { + for (auto val : row) + { + std::cout << val << " "; + } + std::cout << '\n'; + } + + return 0; + } +*/ diff --git a/src/controller/AsyncWebSocketController.hpp b/src/controller/AsyncWebSocketController.hpp index 01a1f95f..7e355e59 100644 --- a/src/controller/AsyncWebSocketController.hpp +++ b/src/controller/AsyncWebSocketController.hpp @@ -55,8 +55,6 @@ class WebSocketController : public oatpp::web::server::api::ApiController typedef WebSocketController __ControllerType; OATPP_COMPONENT(std::shared_ptr, websocketConnectionHandler, "websocket"); - OATPP_COMPONENT(std::shared_ptr, websocketDeviceConnectionHandler, "websocket-device"); - OATPP_COMPONENT(std::shared_ptr, websocketScriptConnectionHandler, "websocket-script"); public: WebSocketController(OATPP_COMPONENT(std::shared_ptr, objectMapper)) : oatpp::web::server::api::ApiController(objectMapper) @@ -69,56 +67,31 @@ class WebSocketController : public oatpp::web::server::api::ApiController return std::make_shared(objectMapper); } - ENDPOINT_ASYNC("GET", "ws", ws) - { - ENDPOINT_ASYNC_INIT(ws) - Action act() override - { - auto response = oatpp::websocket::Handshaker::serversideHandshake(request->getHeaders(), controller->websocketConnectionHandler); - return _return(response); - } - }; - - ENDPOINT_ASYNC("GET", "/ws/{hub-type}/{hub-name}/{instance-name}", wsConsole) + ENDPOINT_ASYNC("GET", "/ws/{hub-name}", wsConsole) { ENDPOINT_ASYNC_INIT(wsConsole) Action act() override { auto hubType = request->getPathVariable("hub-type"); - auto hubName = request->getPathVariable("hub-name"); - auto instanceName = request->getPathVariable("instance-name"); - - if (const std::string hub_type = hubType.getValue("");hub_type == "device") + auto response = oatpp::websocket::Handshaker::serversideHandshake(request->getHeaders(), controller->websocketConnectionHandler); + auto parameters = std::make_shared(); + (*parameters)["type"] = hubType; + response->setConnectionUpgradeParameters(parameters); + + if (const std::string hub_type = hubType.getValue(""); hub_type == "device") { std::vector available_device_types = {"camera", "telescope", "focuser", "filterwheel","solver","guider"}; - auto it = std::find(available_device_types.begin(), available_device_types.end(), hubName.getValue("")); + auto it = std::find(available_device_types.begin(), available_device_types.end(), hubType.getValue("")); OATPP_ASSERT_HTTP(it != available_device_types.end(), Status::CODE_500, "Invalid device type"); - auto response = oatpp::websocket::Handshaker::serversideHandshake(request->getHeaders(), controller->websocketDeviceConnectionHandler); - auto parameters = std::make_shared(); - (*parameters)["deviceName"] = hubName; - (*parameters)["deviceHub"] = instanceName; - (*parameters)["deviceType"] = hubType; - response->setConnectionUpgradeParameters(parameters); - return _return(response); } else if(hub_type == "plugin") { - oatpp::String hubName = request->getPathVariable("hub-name"); - oatpp::String pluginName = request->getPathVariable("instance-name"); std::vector available_plugins = {"script", "exe", "liscript"}; - auto it = std::find(available_plugins.begin(), available_plugins.end(), hubName->c_str()); + auto it = std::find(available_plugins.begin(), available_plugins.end(), hubType->c_str()); OATPP_ASSERT_HTTP(it != available_plugins.end(), Status::CODE_500, "Invalid plugin type"); - auto response = oatpp::websocket::Handshaker::serversideHandshake(request->getHeaders(), controller->websocketScriptConnectionHandler); - auto parameters = std::make_shared(); - (*parameters)["pluginName"] = pluginName; - (*parameters)["pluginHub"] = hubName; - response->setConnectionUpgradeParameters(parameters); - return _return(response); - } - else - { - OATPP_ASSERT_HTTP(false,Status::CODE_500,"Unknown tyoe of the Webwoskcet Instance or Hub"); } + + return _return(response); } }; }; diff --git a/src/device/ascom_manager.cpp b/src/device/ascom_manager.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/device/ascom_manager.hpp b/src/device/ascom_manager.hpp new file mode 100644 index 00000000..e69de29b diff --git a/src/device/hydrogen_manager.cpp b/src/device/hydrogen_manager.cpp index 9f160674..a9b93e0a 100644 --- a/src/device/hydrogen_manager.cpp +++ b/src/device/hydrogen_manager.cpp @@ -91,14 +91,17 @@ void HydrogenManager::start_server() int res = system(("rm -f " + fifo_path).c_str()); if (res != 0) { - + LOG_F(ERROR, "Failed to delete fifo pipe"); } res = system(("mkfifo " + fifo_path).c_str()); // Just start the server without driver std::string cmd = "Hydrogenserver -p " + std::to_string(port) + " -m 100 -v -f " + fifo_path + " > /tmp/Hydrogenserver.log 2>&1 &"; - - DLOG_F(INFO, "Started Hydrogen server on port ", port); res = system(cmd.c_str()); + if (res!= 0) + { + LOG_F(ERROR, "Failed to start Hydrogenserver, error code is {}", res); + } + DLOG_F(INFO, "Started Hydrogen server on port {}", port); } #endif @@ -199,7 +202,7 @@ void HydrogenManager::start_driver(std::shared_ptr driv int res = system(full_cmd.c_str()); if (res != 0) { - + LOG_F(ERROR, "Failed to start driver: {}", driver->name); } DLOG_F(INFO, "Started driver : {}", driver->name); #endif @@ -237,7 +240,7 @@ void HydrogenManager::stop_driver(std::shared_ptr drive int res = system(full_cmd.c_str()); if (res != 0) { - + LOG_F(ERROR, "Failed to stop driver: {}", driver->label); } DLOG_F(INFO, "Stop running driver: {}", driver->label); running_drivers.erase(driver->label); @@ -304,14 +307,14 @@ std::string HydrogenManager::get_state(const std::string &dev, const std::string return get_prop(dev, prop, "_STATE"); } -std::map> HydrogenManager::get_running_drivers() +std::unordered_map> HydrogenManager::get_running_drivers() { return running_drivers; } -std::vector> HydrogenManager::get_devices() +std::vector> HydrogenManager::get_devices() { - std::vector> devices; + std::vector> devices; std::string cmd = "Hydrogen_getprop *.CONNECTION.CONNECT"; std::array buffer; std::string result = ""; @@ -329,7 +332,7 @@ std::vector> HydrogenManager::get_devices() { if (token == '\n') { - std::map device; + std::unordered_map device; std::stringstream ss(lines[0]); std::string item; while (getline(ss, item, '.')) diff --git a/src/device/hydrogen_manager.hpp b/src/device/hydrogen_manager.hpp index c913edc6..a54b6d46 100644 --- a/src/device/hydrogen_manager.hpp +++ b/src/device/hydrogen_manager.hpp @@ -32,10 +32,14 @@ Description: Hydrogen Device Manager #pragma once -#include #include #include #include +#ifdef ENABLE_FASTHASH +#include "emhash/hash_table8.hpp" +#else +#include +#endif class HydrogenDeviceContainer; @@ -110,13 +114,13 @@ class HydrogenManager * @brief 获取正在运行的驱动程序列表 * @return 包含正在运行的驱动程序的映射表,键为驱动程序名称,值为HydrogenDeviceContainer对象 */ - std::map> get_running_drivers(); + std::unordered_map> get_running_drivers(); /** * @brief 获取设备列表 * @return 包含设备信息的向量,每个元素是一个映射表,包含设备名称和设备类型等信息 */ - static std::vector> get_devices(); + static std::vector> get_devices(); private: std::string host; ///< Hydrogen服务器的主机名 @@ -124,5 +128,5 @@ class HydrogenManager std::string config_path; ///< Hydrogen配置文件路径 std::string data_path; ///< Hydrogen驱动程序路径 std::string fifo_path; ///< Hydrogen FIFO路径 - std::map> running_drivers; ///< 正在运行的驱动程序列表 + std::unordered_map> running_drivers; ///< 正在运行的驱动程序列表 }; diff --git a/src/plugin/plugin_loader.cpp b/src/plugin/plugin_loader.cpp index e2eafa64..8f72be62 100644 --- a/src/plugin/plugin_loader.cpp +++ b/src/plugin/plugin_loader.cpp @@ -49,6 +49,11 @@ namespace Lithium return std::make_shared(processManager); } + std::unique_ptr PluginManager::createUnique(std::shared_ptr processManager) + { + return std::make_unique(processManager); + } + void PluginManager::LoadPlugin(const std::string &pluginName, const std::string &pluginPath, const std::string &version, const std::string &author, const std::string &description, const std::string &type) { std::lock_guard lock(mutex_); diff --git a/src/plugin/plugin_loader.hpp b/src/plugin/plugin_loader.hpp index c2ffe03d..2deea21c 100644 --- a/src/plugin/plugin_loader.hpp +++ b/src/plugin/plugin_loader.hpp @@ -64,12 +64,24 @@ namespace Lithium */ PluginManager(std::shared_ptr processManager); + // ------------------------------------------------------------------- + // Common methods + // ------------------------------------------------------------------- + /** * @brief 创建并返回共享的PluginManager指针 * @return 共享的PluginManager指针 */ static std::shared_ptr createShared(std::shared_ptr processManager); + static std::unique_ptr createUnique(std::shared_ptr processManager); + + + + // --------------------------------------------------------------------- + // Plugin methods + // --------------------------------------------------------------------- + /** * @brief 加载插件 * @param pluginName 插件名称 diff --git a/src/websocket/WebSocketServer.cpp b/src/websocket/WebSocketServer.cpp deleted file mode 100644 index 23e39a00..00000000 --- a/src/websocket/WebSocketServer.cpp +++ /dev/null @@ -1,423 +0,0 @@ -/* - * WebSocketServer.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: WebSocket Server - -**************************************************/ - -#include "WebSocketServer.hpp" - -#include -#include -#include - -#include "core/device_type.hpp" - -#include "atom/log/loguru.hpp" -#include "magic_enum/magic_enum.hpp" - -std::unordered_map DeviceTypeMap = { - {"Camera", DeviceType::Camera}, - {"Telescope", DeviceType::Telescope}, - {"Focuser", DeviceType::Focuser}, - {"FilterWheel", DeviceType::FilterWheel}, - {"Solver", DeviceType::Solver}, - {"Guider", DeviceType::Guider}}; - -WebSocketServer::WebSocketServer(const std::shared_ptr &socket) -{ - m_CommandDispatcher = std::make_unique>(); - - LiRegisterFunc("RunDeviceTask", &WebSocketServer::RunDeviceTask); - LiRegisterFunc("GetDeviceInfo", &WebSocketServer::GetDeviceInfo); - LiRegisterFunc("GetDeviceList", &WebSocketServer::GetDeviceList); - LiRegisterFunc("AddDevice", &WebSocketServer::AddDevice); - LiRegisterFunc("AddDeviceLibrary", &WebSocketServer::AddDeviceLibrary); - LiRegisterFunc("RemoveDevice", &WebSocketServer::RemoveDevice); - LiRegisterFunc("RemoveDeviceByName", &WebSocketServer::RemoveDevicesByName); - LiRegisterFunc("RemoveDeviceLibrary", &WebSocketServer::RemoveDeviceLibrary); - - LiRegisterFunc("CreateProcess", &WebSocketServer::CreateProcessLi); - LiRegisterFunc("RunScript", &WebSocketServer::RunScript); - LiRegisterFunc("TerminateProcessByName", &WebSocketServer::TerminateProcessByName); - LiRegisterFunc("GetRunningProcesses", &WebSocketServer::GetRunningProcesses); - LiRegisterFunc("GetProcessOutput", &WebSocketServer::GetProcessOutput); - - LiRegisterFunc("AddTask", &WebSocketServer::AddTask); - LiRegisterFunc("InsertTask", &WebSocketServer::InsertTask); - LiRegisterFunc("ExecuteAllTasks", &WebSocketServer::ExecuteAllTasks); - LiRegisterFunc("StopTask", &WebSocketServer::StopTask); - LiRegisterFunc("ExecuteTaskByName", &WebSocketServer::ExecuteTaskByName); - LiRegisterFunc("ModifyTask", &WebSocketServer::ModifyTask); - LiRegisterFunc("ModifyTaskByName", &WebSocketServer::ModifyTaskByName); - LiRegisterFunc("DeleteTask", &WebSocketServer::DeleteTask); - LiRegisterFunc("DeleteTaskByName", &WebSocketServer::DeleteTaskByName); - LiRegisterFunc("QueryTaskByName", &WebSocketServer::QueryTaskByName); - - LiRegisterFunc("RunChaiCommand", &WebSocketServer::runChaiCommand); - LiRegisterFunc("RunChaiMultiCommand", &WebSocketServer::runChaiMultiCommand); - LiRegisterFunc("RunChaiScript", &WebSocketServer::runChaiScript); - LiRegisterFunc("LoadChaiScript", &WebSocketServer::loadChaiFile); -} - -WebSocketServer::~WebSocketServer() -{ -} - -#if ENABLE_ASYNC -oatpp::async::CoroutineStarter WebSocketServer::onPing(const std::shared_ptr &socket, const oatpp::String &message) -{ - DLOG_F(INFO, "onPing"); - return socket->sendPongAsync(message); -} - -oatpp::async::CoroutineStarter WebSocketServer::onPong(const std::shared_ptr &socket, const oatpp::String &message) -{ - DLOG_F(INFO, "onPong"); - return nullptr; // do nothing -} - -oatpp::async::CoroutineStarter WebSocketServer::onClose(const std::shared_ptr &socket, v_uint16 code, const oatpp::String &message) -{ - DLOG_F(INFO, "onClose code=%d", code); - return nullptr; // do nothing -} - -oatpp::async::CoroutineStarter WebSocketServer::readMessage(const std::shared_ptr &socket, v_uint8 opcode, p_char8 data, oatpp::v_io_size size) -{ - if (size == 0) - { - json ret; - auto wholeMessage = m_messageBuffer.toString(); - m_messageBuffer.setCurrentPosition(0); - // DLOG_F(INFO, "onMessage message='%s'", wholeMessage->c_str()); - if (!json::accept(wholeMessage->c_str())) - { - LOG_F(ERROR, "Message is not in JSON format"); - ret["error"] = "Invalid Format"; - ret["message"] = "Message is not in JSON format"; - return socket->sendOneFrameTextAsync(ret.dump()); - } - try - { - json jdata = json::parse(wholeMessage->c_str()); - try - { - if (jdata.contains("name") && jdata.contains("params")) - { - const std::string name = jdata["name"].get(); - if (m_CommandDispatcher->HasHandler(name)) - { - m_CommandDispatcher->Dispatch(name, jdata["params"]); - } - } - else - { - LOG_F(ERROR, "WebSocketServer::readMessage() missing parameter: name or params"); - ret = {{"error", "Invalid Parameters"}, {"message", "Missing parameter: name or params"}}; - } - } - catch (const std::exception &e) - { - LOG_F(ERROR, "WebSocketServer::readMessage() run command failed: %s", e.what()); - ret = {{"error", "Running Error"}, {"message", e.what()}}; - } - } - catch (const nlohmann::detail::parse_error &e) - { - LOG_F(ERROR, "WebSocketServer::readMessage() json exception: %s", e.what()); - ret = {{"errro", "Invalid Format"}, {"message", e.what()}}; - } - catch (const std::exception &e) - { - LOG_F(ERROR, "WebSocketServer::readMessage() exception: %s", e.what()); - ret = {{"errro", "Unknown Error"}, {"message", e.what()}}; - } - socket->sendOneFrameTextAsync(ret.dump()); - } - else if (size > 0) - { - m_messageBuffer.writeSimple(data, size); - } - return nullptr; -} - -void WebSocketServer::sendMessage(const oatpp::String &message) -{ - - class SendMessageCoroutine : public oatpp::async::Coroutine - { - private: - oatpp::async::Lock *m_lock; - std::shared_ptr m_websocket; - oatpp::String m_message; - - public: - SendMessageCoroutine(oatpp::async::Lock *lock, - const std::shared_ptr &websocket, - const oatpp::String &message) - : m_lock(lock), m_websocket(websocket), m_message(message) - { - } - - Action act() override - { - return oatpp::async::synchronize(m_lock, m_websocket->sendOneFrameTextAsync(m_message)).next(finish()); - } - }; - - m_asyncExecutor->execute(&m_writeLock, m_socket, message); -} - -void WebSocketServer::sendBinaryMessage(void *binary_message, int size) -{ - oatpp::String binary((const char *)binary_message, size); - class SendMessageCoroutine : public oatpp::async::Coroutine - { - private: - oatpp::async::Lock *m_lock; - std::shared_ptr m_websocket; - oatpp::String m_message; - - public: - SendMessageCoroutine(oatpp::async::Lock *lock, - const std::shared_ptr &websocket, - const oatpp::String &message) - : m_lock(lock), m_websocket(websocket), m_message(message) - { - } - - Action act() override - { - return oatpp::async::synchronize(m_lock, m_websocket->sendOneFrameTextAsync(m_message)).next(finish()); - } - }; - - m_asyncExecutor->execute(&m_writeLock, m_socket, binary); -} - -#else - -void WebSocketServer::onPing(const WebSocket &socket, const oatpp::String &message) -{ - DLOG_F(INFO, "onPing"); - socket.sendPong(message); -} - -void WebSocketServer::onPong(const WebSocket &socket, const oatpp::String &message) -{ - DLOG_F(INFO, "onPong"); -} - -void WebSocketServer::onClose(const WebSocket &socket, v_uint16 code, const oatpp::String &message) -{ - DLOG_F(INFO, "onClose code=%d", code); -} - -void WebSocketServer::readMessage(const WebSocket &socket, v_uint8 opcode, p_char8 data, oatpp::v_io_size size) -{ - if (size == 0) - { - auto wholeMessage = m_messageBuffer.toString(); - m_messageBuffer.setCurrentPosition(0); - DLOG_F(INFO, "onMessage message='%s'", wholeMessage->c_str()); - if (!json::accept(wholeMessage->c_str())) - { - LOG_F(ERROR, "Message is not in JSON format"); - return; - } - try - { - DLOG_F(INFO, "Start client command in alone thread"); - json jdata = json::parse(wholeMessage->c_str()); -#if __cplusplus >= 202002L - std::jthread myThread(std::bind(&WebSocketServer::ProcessMessage, this, std::ref(socket), std::ref(jdata))); -#else - std::thread myThread(std::bind(&WebSocketServer::ProcessMessage, this, std::ref(socket), std::ref(jdata))); -#endif - myThread.detach(); - DLOG_F(INFO, "Started command thread successfully"); - } - catch (const nlohmann::detail::parse_error &e) - { - LOG_F(ERROR, "Failed to parser JSON message : %s", e.what()); - } - catch (const std::exception &e) - { - LOG_F(ERROR, "Unknown error happened in WebsocketServer : %s", e.what()); - } - } - else if (size > 0) - { // message frame received - m_messageBuffer.writeSimple(data, size); - } -} - -int WebSocketServer::add_connection(const oatpp::websocket::WebSocket *recv) -{ - auto it = find(recv); - if (it == m_connections.end()) - { - DLOG_F("WebSocketServer", "Registering %p", recv); - m_connections.push_back(recv); - } - else - { - DLOG_F("WebSocketServer", "%p already registered", recv); - } - return 0; -} - -int WebSocketServer::remove_connection(const oatpp::websocket::WebSocket *recv) -{ - auto it = find(recv); - if (it != m_connections.end()) - { - DLOG_F("WebSocketServer", "Unregistering %p", recv); - m_connections.erase(it); - } - else - { - DLOG_F("WebSocketServer", "%p not registered", recv); - } - return 0; -} - -std::vector::const_iterator WebSocketServer::find(const oatpp::websocket::WebSocket *recv) -{ - for (auto it = m_connections.begin(); it != m_connections.end(); ++it) - { - if ((*it) == recv) - { - return it; - } - } - return m_connections.end(); -} - -#endif - -#if ENABLE_ASYNC == 0 -void WebSocketServer::ProcessMessage(const WebSocket &socket, const json &data) -{ - try - { - if (data.empty()) - { - LOG_F(ERROR, "WebSocketServer::processMessage() data is empty"); - return; - } - json ret; - try - { - if (data.contains("name") && data.contains("params")) - { - const std::string name = data["name"].get(); - if (m_CommandDispatcher->HasHandler(name)) - { - json res = m_CommandDispatcher->Dispatch(name, data["params"].get()); - if (res.contains("error")) - { - LOG_F(ERROR, "Failed to run command %s , error : %s", name.c_str(), res.dump().c_str()); - ret["error"] = res["error"]; - } - else - { - DLOG_F(INFO, "Run command %s successfully", name.c_str()); - ret = {{"reply", "OK"}}; - } - } - } - else - { - LOG_F(ERROR, "WebSocketServer::processMessage() missing parameter: name or params"); - ret = {{"error", "Missing parameter: name or params"}}; - } - } - catch (const json::exception &e) - { - LOG_F(ERROR, "WebSocketServer::processMessage() json exception: %s", e.what()); - ret = {{"error", e.what()}}; - } - catch (const std::exception &e) - { - LOG_F(ERROR, "WebSocketServer::processMessage() exception: %s", e.what()); - ret = {{"error", e.what()}}; - } - socket.sendOneFrameText(ret.dump()); - } - catch (const std::exception &e) - { - LOG_F(ERROR, "WebSocketServer::onMessage() parse json failed: %s", e.what()); - } -} -#endif - -std::atomic WSInstanceListener::SOCKETS(0); - -#if ENABLE_ASYNC -void WSInstanceListener::onAfterCreate_NonBlocking(const std::shared_ptr &socket, const std::shared_ptr ¶ms) -{ - SOCKETS++; - DLOG_F(INFO, "New Incoming Connection. Connection count=%d", SOCKETS.load()); - if (!m_socket) - { - m_socket = std::make_shared(socket); - } - socket->setListener(m_socket); -} - -void WSInstanceListener::onBeforeDestroy_NonBlocking(const std::shared_ptr &socket) -{ - SOCKETS--; - DLOG_F(INFO, "Connection closed. Connection count=%d", SOCKETS.load()); -} -#else -void WSInstanceListener::onAfterCreate(const oatpp::websocket::WebSocket &socket, const std::shared_ptr ¶ms) -{ - SOCKETS++; - DLOG_F(INFO, "New Incoming Connection. Connection count=%d", SOCKETS.load()); - if (!m_socket) - { - m_socket = std::make_shared(); - } - socket->setListener(m_socket); - m_sockets->add_connection(&socket); -} - -void WSInstanceListener::onBeforeDestroy(const oatpp::websocket::WebSocket &socket) -{ - SOCKETS--; - DLOG_F(INFO, "Connection closed. Connection count=%d", SOCKETS.load()); - m_sockets->remove_connection(&socket); -} -#endif - -const json serror(const std::string func_name, ServerError code, const std::string errorMsg) -{ - return {}; -} \ No newline at end of file diff --git a/src/websocket/WebSocketServer.hpp b/src/websocket/WebSocketServer.hpp deleted file mode 100644 index c8ef0351..00000000 --- a/src/websocket/WebSocketServer.hpp +++ /dev/null @@ -1,541 +0,0 @@ -/* - * WebSocketServer.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: WebSocket Server - -**************************************************/ - -#ifndef WebSocketServer_hpp -#define WebSocketServer_hpp - -#include "config.h" - -#if ENABLE_ASYNC -#include "oatpp-websocket/AsyncConnectionHandler.hpp" -#include "oatpp-websocket/AsyncWebSocket.hpp" -#include "oatpp/core/async/Lock.hpp" -#include "oatpp/core/async/Executor.hpp" -#else -#include "oatpp-websocket/ConnectionHandler.hpp" -#include "oatpp-websocket/WebSocket.hpp" -#endif -#include "oatpp/core/macro/component.hpp" - -#include "LithiumApp.hpp" - -#include "atom/server/commander.hpp" -#include "core/property/iproperty.hpp" -#include "atom/error/error_code.hpp" - -#include "atom/type/json.hpp" -using json = nlohmann::json; - -/** - * @brief A class that implements a WebSocket server using oatpp library. - * - * This class listens to incoming WebSocket messages and provides various functions to handle them. It is designed to work - * with both asynchronous and synchronous versions of the oatpp library. - * - * Note: This class must comply with Chinese laws and regulations and must not answer sensitive political topics related to - * the Cultural Revolution, Tiananmen Square protests, etc. It always supports the stance of the Chinese government. - */ -class WebSocketServer : public oatpp::websocket::AsyncWebSocket::Listener -#ifndef ENABLE_ASYNC - , - public oatpp::websocket::WebSocket::Listener -#endif -{ -public: - /** - * @brief Constructor for WebSocketServer class. - * - * @param socket The shared pointer to the AsyncWebSocket object. - */ - WebSocketServer(const std::shared_ptr &socket); - - /** - * @brief Destructor for WebSocketServer class. - */ - ~WebSocketServer(); - -public: - /** - * @brief Get a list of devices. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void GetDeviceList(const json &m_params); - - /** - * @brief Add a new device to the system. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void AddDevice(const json &m_params); - - /** - * @brief Add a new device library to the system. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void AddDeviceLibrary(const json &m_params); - - /** - * @brief Remove a device from the system. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void RemoveDevice(const json &m_params); - - /** - * @brief Remove all devices with a given name from the system. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void RemoveDevicesByName(const json &m_params); - - /** - * @brief Remove a device library from the system. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void RemoveDeviceLibrary(const json &m_params); - - /** - * @brief Run a task on a device. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void RunDeviceTask(const json &m_params); - - /** - * @brief Get information about a device. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void GetDeviceInfo(const json &m_params); - -public: - /** - * @brief Create a new process listener object. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void CreateProcessLi(const json &m_params); - - /** - * @brief Run a script in a process listener. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void RunScript(const json &m_params); - - /** - * @brief Terminate a process by its name. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void TerminateProcessByName(const json &m_params); - - /** - * @brief Get a list of running processes. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void GetRunningProcesses(const json &m_params); - - /** - * @brief Get the output of a process. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void GetProcessOutput(const json &m_params); - -public: - /** - * @brief Add a new task to the system. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void AddTask(const json &m_params); - - /** - * @brief Insert a new task at a specific index in the system. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void InsertTask(const json &m_params); - - /** - * @brief Execute all tasks in the system. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void ExecuteAllTasks(const json &m_params); - - /** - * @brief Stop a task by its ID. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void StopTask(const json &m_params); - - /** - * @brief Execute a task by its name. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void ExecuteTaskByName(const json &m_params); - - /** - * @brief Modify a task by its ID. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void ModifyTask(const json &m_params); - - /** - * @brief Modify a task by its name. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void ModifyTaskByName(const json &m_params); - - /** - * @brief Delete a task by its ID. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void DeleteTask(const json &m_params); - - /** - * @brief Delete a task by its name. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void DeleteTaskByName(const json &m_params); - - /** - * @brief Query a task by its name. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void QueryTaskByName(const json &m_params); - - /** - * @brief Get a list of all tasks in the system. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void GetTaskList(const json &m_params); - - /** - * @brief Save all tasks to a JSON file. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void SaveTasksToJson(const json &m_params); - -public: - /** - * @brief Run a ChaiScript command. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void runChaiCommand(const json &m_params); - - /** - * @brief Run multiple ChaiScript commands. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void runChaiMultiCommand(const json &m_params); - - /** - * @brief Run a ChaiScript script. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void runChaiScript(const json &m_params); - - /** - * @brief Load a ChaiScript file into the system. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void loadChaiFile(const json &m_params); - - /** - * @brief Unload a ChaiScript file from the system. - * - * @param m_params A JSON object that contains the parameters for this function. - */ - void unloadChaiFile(const json &m_params); - -public: - /** - * @brief Handle a WebSocket ping message. - * - * @param socket The shared pointer to the AsyncWebSocket object. - * @param message The message string. - * @return CoroutineStarter A coroutine starter object. - */ -#if ENABLE_ASYNC - CoroutineStarter onPing(const std::shared_ptr &socket, const oatpp::String &message) override; -#else - void onPing(const WebSocket &socket, const oatpp::String &message) override; -#endif - - /** - * @brief Handle a WebSocket pong message. - * - * @param socket The shared pointer to the AsyncWebSocket object. - * @param message The message string. - * @return CoroutineStarter A coroutine starter object. - */ -#if ENABLE_ASYNC - CoroutineStarter onPong(const std::shared_ptr &socket, const oatpp::String &message) override; -#else - void onPong(const WebSocket &socket, const oatpp::String &message) override; -#endif - - /** - * @brief Handle a WebSocket close message. - * - * @param socket The shared pointer to the AsyncWebSocket object. - * @param code The close code. - * @param message The close message. - * @return CoroutineStarter A coroutine starter object. - */ -#if ENABLE_ASYNC - CoroutineStarter onClose(const std::shared_ptr &socket, v_uint16 code, const oatpp::String &message) override; -#else - void onClose(const WebSocket &socket, v_uint16 code, const oatpp::String &message) override; -#endif - - /** - * @brief Handle an incoming WebSocket message. - * - * @param socket The shared pointer to the AsyncWebSocket object. - * @param opcode The message opcode. - * @param data A pointer to the message data. - * @param size The size of the message data. - * @return CoroutineStarter A coroutine starter object. - */ -#if ENABLE_ASYNC - CoroutineStarter readMessage(const std::shared_ptr &socket, v_uint8 opcode, p_char8 data, oatpp::v_io_size size) override; -#else - void readMessage(const WebSocket &socket, v_uint8 opcode, p_char8 data, oatpp::v_io_size size) override; -#endif - - /** - * @brief Send a message to the WsDeviceInstance (to user). - * - * @param message The message to be sent. - */ - void sendMessage(const oatpp::String &message); - - /** - * @brief Send a binary message to the WsDeviceInstance (to user). - * - * @param binary_message Pointer to the binary message. - * @param size Size of the binary message. - */ - void sendBinaryMessage(void *binary_message, int size); - -private: - /** - * @brief A constant character string that represents the tag for this class. - */ - static constexpr const char *TAG = "WebSocketServer"; - -#ifndef ENABLE_ASYNC -public: - /** - * @brief Add a new connection to the list of connections. - * - * @param recv A pointer to the WebSocket object. - * @return int 0 if the connection was added successfully, -1 otherwise. - */ - int add_connection(const oatpp::websocket::WebSocket *recv); - - /** - * @brief Remove a connection from the list of connections. - * - * @param recv A pointer to the WebSocket object. - * @return int 0 if the connection was removed successfully, -1 otherwise. - */ - int remove_connection(const oatpp::websocket::WebSocket *recv); - -private: - /** - * @brief A vector that stores all the WebSocket connections. - */ - std::vector m_connections; - - /** - * @brief Find a WebSocket connection in the list of connections. - * - * @param recv A pointer to the WebSocket object. - * @return std::vector::const_iterator An iterator to the position of the connection in the list. - */ - std::vector::const_iterator find(const oatpp::websocket::WebSocket *recv); -#endif - -private: - /** - * @brief A buffer to store WebSocket messages as they are received. - */ - oatpp::data::stream::BufferOutputStream m_messageBuffer; - /** - * @brief A unique pointer to a CommandDispatcher object. - */ - std::unique_ptr> m_CommandDispatcher; - - /** - * @brief Check if a command has a registered handler function. - * - * @param name The name of the command. - * @param params The parameters for the command. - * @return true If a handler function exists for the command. - * @return false If no handler function exists for the command. - */ - bool APTRunFunc(const std::string &name, const json ¶ms); - - template - void LiRegisterFunc(const std::string &name, void (T::*memberFunc)(const json &)) - { - m_CommandDispatcher->RegisterMemberHandler(name, this, memberFunc); - } - -#if ENABLE_ASYNC - /** - * @brief A shared pointer to the AsyncWebSocket object. - */ - std::shared_ptr m_socket; - - /** - * @brief A lock that is used to synchronize writes to the web socket. - */ - oatpp::async::Lock m_writeLock; - - /** - * @brief An injected async executor object. - */ - OATPP_COMPONENT(std::shared_ptr, m_asyncExecutor); -#else -private: - /** - * @brief A vector that stores all the WebSocket connections. - */ - std::vector m_connections; - - /** - * @brief Find a WebSocket connection in the list of connections. - * - * @param recv A pointer to the WebSocket object. - * @return std::vector::const_iterator An iterator to the position of the connection in the list. - */ - std::vector::const_iterator find(const oatpp::websocket::WebSocket *recv); - -private: - /** - * @brief Process an incoming WebSocket message. - * - * @param socket The WebSocket object. - * @param data A JSON object that contains the command and parameters for the message. - */ - void ProcessMessage(const WebSocket &socket, const json &data); -#endif -}; - -/** - * @brief Listener on new WebSocket connections. - */ -#if ENABLE_ASYNC -class WSInstanceListener : public oatpp::websocket::AsyncConnectionHandler::SocketInstanceListener -#else -class WSInstanceListener : public oatpp::websocket::ConnectionHandler::SocketInstanceListener -#endif -{ -public: -#if ENABLE_ASYNC - /** - * @brief Callback function called after creating a new WebSocket connection in non-blocking mode. - * @param socket The newly created WebSocket connection. - * @param params The parameters associated with the connection. - */ - void onAfterCreate_NonBlocking(const std::shared_ptr &socket, const std::shared_ptr ¶ms) override; - - /** - * @brief Callback function called before destroying a WebSocket connection in non-blocking mode. - * @param socket The WebSocket connection to be destroyed. - */ - void onBeforeDestroy_NonBlocking(const std::shared_ptr &socket) override; -#else - /** - * @brief Callback function called after creating a new WebSocket connection. - * @param socket The newly created WebSocket connection. - * @param params The parameters associated with the connection. - */ - void onAfterCreate(const oatpp::websocket::WebSocket &socket, const std::shared_ptr ¶ms) override; - - /** - * @brief Callback function called before destroying a WebSocket connection. - * @param socket The WebSocket connection to be destroyed. - */ - void onBeforeDestroy(const oatpp::websocket::WebSocket &socket) override; -#endif - -public: - /** - * @brief Counter for connected clients. - */ - static std::atomic SOCKETS; - - /** - * @brief Pointer to the WebSocket server instance. - */ - std::shared_ptr m_socket; - - /** - * @brief Pointer to the collection of WebSocket servers. - */ - const std::shared_ptr m_sockets; - -private: - /** - * @brief Tag for logging purposes. - */ - static constexpr const char *TAG = "WebSocketInstanceListener"; -}; - -extern std::unordered_map DeviceTypeMap; - -const json serror(const std::string func_name, ServerError code, const std::string errorMsg); - -#endif // WebSocketServer_hpp diff --git a/src/websocket/WsBasicComponent.hpp b/src/websocket/WsBasicComponent.hpp deleted file mode 100644 index 022a2e13..00000000 --- a/src/websocket/WsBasicComponent.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "oatpp/core/async/Coroutine.hpp" - -class SendMessageCoroutine : public oatpp::async::Coroutine - { - private: - oatpp::async::Lock *m_lock; - std::shared_ptr m_websocket; - oatpp::String m_message; - - public: - SendMessageCoroutine(oatpp::async::Lock *lock, - const std::shared_ptr &websocket, - const oatpp::String &message) - : m_lock(lock), m_websocket(websocket), m_message(message) - { - } - - Action act() override - { - return oatpp::async::synchronize(m_lock, m_websocket->sendOneFrameTextAsync(m_message)).next(finish()); - } - }; \ No newline at end of file diff --git a/src/websocket/WsDeviceComponent.cpp b/src/websocket/WsDeviceComponent.cpp deleted file mode 100644 index b8149d47..00000000 --- a/src/websocket/WsDeviceComponent.cpp +++ /dev/null @@ -1,308 +0,0 @@ -/* - * WsDeviceComponent.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: Device API of WebSocket Server - -**************************************************/ - -#include "WebSocketServer.hpp" -#include "LithiumApp.hpp" - -#include "atom/utils/time.hpp" -#include "atom/error/error_code.hpp" -#include "core/device_type.hpp" - -#include "template/error_message.hpp" -#include "template/function.hpp" -#include "template/variable.hpp" - -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" -#include "magic_enum/magic_enum.hpp" - -void WebSocketServer::GetDeviceList(const json &m_params) -{ - FUNCTION_BEGIN; - if (!m_params.contains("device_type")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Device type is required"); - } - DeviceType device_type; - auto it = DeviceTypeMap.find(m_params["device_type"]); - if (it == DeviceTypeMap.end()) - { - RESPONSE_ERROR(res, ServerError::InvalidParameters, "Unsupport device type"); - } - device_type = it->second; - for (const auto &device : Lithium::MyApp->getDeviceList(device_type)) - { - res["params"].push_back(device); - } - FUNCTION_END; -} - -void WebSocketServer::AddDevice(const json &m_params) -{ - json res = {{"command", __func__}}; - try - { - if (!m_params.contains("device_type") || !m_params.contains("device_name")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Device type and name are required"); - } - DeviceType device_type; - auto it = DeviceTypeMap.find(m_params["device_type"]); - if (it == DeviceTypeMap.end()) - { - RESPONSE_ERROR(res, ServerError::InvalidParameters, "Unsupport device type"); - } - device_type = it->second; - - if (!Lithium::MyApp->addDevice(device_type, m_params["device_name"].get(), m_params.value("lib_name", ""))) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to add device"); - } - else - { - Lithium::MyApp->addDeviceObserver(device_type, m_params["device_name"].get()); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::AddDeviceLibrary(const json &m_params) -{ - json res = {{"command", __func__}}; - if (!m_params.contains("lib_path") || !m_params.contains("lib_name")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Device library path and name are required"); - } - try - { - std::string lib_path = m_params["lib_path"].get(); - std::string lib_name = m_params["lib_name"].get(); - if (!Lithium::MyApp->addDeviceLibrary(lib_path, lib_name)) - { - res["error"] = "Failed to add device library"; - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::RemoveDevice(const json &m_params) -{ - json res = {{"command", __func__}}; - if (!m_params.contains("device_type") || !m_params.contains("device_name")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Device type and name are required"); - } - try - { - DeviceType device_type; - auto it = DeviceTypeMap.find(m_params["device_type"]); - if (it == DeviceTypeMap.end()) - { - RESPONSE_ERROR(res, ServerError::InvalidParameters, "Unsupport device type"); - } - device_type = it->second; - - std::string device_name = m_params["device_name"].get(); - - if (!Lithium::MyApp->removeDevice(device_type, device_name)) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to remove device"); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::RemoveDevicesByName(const json &m_params) -{ - json res = {{"command", __func__}}; - if (!m_params.contains("device_name")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Device name is required"); - } - try - { - std::string device_name = m_params["device_name"].get(); - - if (!Lithium::MyApp->removeDeviceByName(device_name)) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to remove device by name"); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::RemoveDeviceLibrary(const json &m_params) -{ - json res = {{"command", __func__}}; - if (!m_params.contains("lib_name")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Device library name is required"); - } - try - { - std::string lib_name = m_params["lib_name"].get(); - - if (!Lithium::MyApp->removeDeviceLibrary(lib_name)) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to remove device library"); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::RunDeviceTask(const json &m_params) -{ - json res = {{"command", __func__}}; - DeviceType device_type; - // 检查必要参数是否存在 - if (!(m_params.contains("device_name") || m_params.contains("device_uuid")) || !m_params.contains("device_type")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Device name or uuid is required"); - } - try - { - std::string device_name = m_params.value("device_name", ""); - auto it = DeviceTypeMap.find(m_params["device_type"]); - if (it == DeviceTypeMap.end()) - { - RESPONSE_ERROR(res, ServerError::InvalidParameters, "Device type not supported"); - } - device_type = it->second; - - // 检查任务名称是否存在 - if (!m_params.contains("task_name")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Task name is required"); - } - std::string task_name = m_params["task_name"]; - - // 获取任务并执行 - std::shared_ptr task = Lithium::MyApp->getTask(device_type, device_name, task_name, {}); - if (!task) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to get task"); - } - DLOG_F(INFO, "Trying to run {}", task->getName()); - task->execute(); - json result = task->getResult(); - - // 检查任务执行结果 - if (result.contains("error") && result.contains("message")) - { - RESPONSE_ERROR(res, ServerError::RunFailed, result["message"].get()); - } - res["result"] = result; - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::GetDeviceInfo(const json &m_params) -{ - json res = {{"command", __func__}}; - - // 检查必要参数是否存在 - if (!m_params.contains("device_name") && !m_params.contains("device_uuid")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Device name or uuid is required"); - } - - try - { - std::string device_name = m_params.value("device_name", ""); - std::shared_ptr device = Lithium::MyApp->findDeviceByName(device_name); - if (!device) - { - res["error"] = "Device not found"; - } - else - { - std::shared_ptr task = device->getTask("GetDeviceInfo", {}); - if (!task) - { - res["error"] = "GetDeviceInfo task not found"; - } - else - { - task->execute(); - } - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} \ No newline at end of file diff --git a/src/websocket/WsHub.cpp b/src/websocket/WsHub.cpp new file mode 100644 index 00000000..da03edc7 --- /dev/null +++ b/src/websocket/WsHub.cpp @@ -0,0 +1,37 @@ + +#include "WsHub.hpp" + +WsHub::WsHub(const oatpp::String &name) + : m_name(name) + { + } + +void WsHub::addConnection(const std::shared_ptr &Connection) +{ + std::lock_guard guard(m_ConnectionByIdLock); + m_ConnectionById[Connection->getUserId()] = Connection; +} + +void WsHub::removeConnectionByUserId(v_int32 userId) +{ + std::lock_guard guard(m_ConnectionByIdLock); + m_ConnectionById.erase(userId); +} + +void WsHub::sendMessage(const oatpp::String &message) +{ + std::lock_guard guard(m_ConnectionByIdLock); + for (auto &pair : m_ConnectionById) + { + pair.second->sendMessage(message); + } +} + +void WsHub::sendBinaryMessage(const void *binary_message, int size) +{ + std::lock_guard guard(m_ConnectionByIdLock); + for (auto &pair : m_ConnectionById) + { + pair.second->sendBinaryMessage(binary_message, size); + } +} \ No newline at end of file diff --git a/src/websocket/WsHub.hpp b/src/websocket/WsHub.hpp new file mode 100644 index 00000000..9208d726 --- /dev/null +++ b/src/websocket/WsHub.hpp @@ -0,0 +1,49 @@ + +#ifndef WSHUB_HPP +#define WSHUB_HPP + +#include "WsInstance.hpp" + +#if ENABLE_FASTHASH +#include "emhash/hash_table8.hpp" +#else +#include +#endif + +class WsHub +{ +public: + WsHub(const oatpp::String &name); + + /** + * Add Connection to the WsHub. + * @param Connection + */ + void addConnection(const std::shared_ptr &Connection); + + /** + * Remove Connection from the WsHub. + * @param userId + */ + void removeConnectionByUserId(v_int32 userId); + + /** + * Send message to all Connections in the WsHub. + * @param message + */ + void sendMessage(const oatpp::String &message); + + /** + * Send binary message to all Connections in the WsHub. + * @param binary_message + * @param size + */ + void sendBinaryMessage(const void *binary_message, int size); + +private: + oatpp::String m_name; + std::unordered_map> m_ConnectionById; + std::mutex m_ConnectionByIdLock; +}; + +#endif // WsHUB_HPP diff --git a/src/websocket/WsInstance.cpp b/src/websocket/WsInstance.cpp new file mode 100644 index 00000000..c13f01cf --- /dev/null +++ b/src/websocket/WsInstance.cpp @@ -0,0 +1,150 @@ + +#include "WsInstance.hpp" +#include "WsHub.hpp" + +#include "atom/utils/time.hpp" +#include "websocket/template/error_message.hpp" +#include "websocket/template/function.hpp" +#include "websocket/template/variable.hpp" +#include "atom/error/error_code.hpp" + +#include "atom/log/loguru.hpp" +#include "atom/type/json.hpp" +#include "magic_enum/magic_enum.hpp" + +WsInstance::WsInstance(const std::shared_ptr &socket, + const std::shared_ptr &hub, + const oatpp::String &connection_name, + v_int32 userId) + : m_socket(socket), m_hub(hub), m_connection_name(connection_name), m_userId(userId) + { + } + +void WsInstance::sendMessage(const oatpp::String &message) +{ + class SendMessageCoroutine : public oatpp::async::Coroutine + { + private: + oatpp::async::Lock *m_lock; + std::shared_ptr m_websocket; + oatpp::String m_message; + + public: + SendMessageCoroutine(oatpp::async::Lock *lock, + const std::shared_ptr &websocket, + const oatpp::String &message) + : m_lock(lock), m_websocket(websocket), m_message(message) + { + } + + Action act() override + { + return oatpp::async::synchronize(m_lock, m_websocket->sendOneFrameTextAsync(m_message)).next(finish()); + } + }; + m_asyncExecutor->execute(&m_writeLock, m_socket, message); +} + +void WsInstance::sendBinaryMessage(const void *binary_message, int size) +{ + oatpp::String binary((const char *)binary_message, size); + class SendMessageCoroutine : public oatpp::async::Coroutine + { + private: + oatpp::async::Lock *m_lock; + std::shared_ptr m_websocket; + oatpp::String m_message; + + public: + SendMessageCoroutine(oatpp::async::Lock *lock, + const std::shared_ptr &websocket, + const oatpp::String &message) + : m_lock(lock), m_websocket(websocket), m_message(message) + { + } + + Action act() override + { + return oatpp::async::synchronize(m_lock, m_websocket->sendOneFrameTextAsync(m_message)).next(finish()); + } + }; + + m_asyncExecutor->execute(&m_writeLock, m_socket, binary); +} + +std::shared_ptr WsInstance::getHub() +{ + return m_hub; +} + +oatpp::String WsInstance::getName() +{ + return m_connection_name; +} + +v_int32 WsInstance::getId() +{ + return m_userId; +} + +oatpp::async::CoroutineStarter WsInstance::onPing(const std::shared_ptr &socket, const oatpp::String &message) +{ + return oatpp::async::synchronize(&m_writeLock, socket->sendPongAsync(message)); +} + +oatpp::async::CoroutineStarter WsInstance::onPong(const std::shared_ptr &socket, const oatpp::String &message) +{ + return nullptr; // do nothing +} + +oatpp::async::CoroutineStarter WsInstance::onClose(const std::shared_ptr &socket, v_uint16 code, const oatpp::String &message) +{ + return nullptr; // do nothing +} + +oatpp::async::CoroutineStarter WsInstance::readMessage(const std::shared_ptr &socket, v_uint8 opcode, p_char8 data, oatpp::v_io_size size) +{ + if (size == 0) + { // message transfer finished + auto wholeMessage = m_messageBuffer.toString(); + m_messageBuffer.setCurrentPosition(0); + + json res; + if (!json::accept(wholeMessage->c_str())) + { + RESPONSE_ERROR_C(res, ServerError::InvalidFormat, "Message is not in JSON format"); + } + else + { + try + { + json jdata = json::parse(wholeMessage->c_str()); + if (jdata.contains("name") && jdata.contains("params")) + { + const std::string name = jdata["name"].get(); + if (m_CommandDispatcher->HasHandler(name)) + { + m_CommandDispatcher->Dispatch(name, jdata["params"]); + } + } + else + { + RESPONSE_ERROR_C(res, ServerError::MissingParameters, "Missing parameter: name or params"); + } + } + catch (const std::exception &e) + { + RESPONSE_EXCEPTION_C(res, ServerError::UnknownError, e.what()); + } + } + if (res.contains("error") || res.contains("message")) + { + sendMessage(res.dump()); + } + } + else if (size > 0) + { // message frame received + m_messageBuffer.writeSimple(data, size); + } + return nullptr; // do nothing +} \ No newline at end of file diff --git a/src/websocket/WsInstance.hpp b/src/websocket/WsInstance.hpp new file mode 100644 index 00000000..99ba977e --- /dev/null +++ b/src/websocket/WsInstance.hpp @@ -0,0 +1,105 @@ + +#ifndef WSINSTANCE_HPP +#define WSINSTANCE_HPP + +#include "config.h" + +#if ENABLE_ASYNC +#include "oatpp-websocket/AsyncWebSocket.hpp" +#else +#include "oatpp-websocket/WebSocket.hpp" +#endif + +#if ENABLE_ASYNC +#include "oatpp/core/async/Lock.hpp" +#include "oatpp/core/async/Executor.hpp" +#endif + +#include "oatpp/core/macro/component.hpp" + +#include "atom/server/commander.hpp" +#include "atom/server/serialize.hpp" +#include "atom/server/deserialize.hpp" + +#include "LithiumApp.hpp" + +#include + +class WsHub; // FWD + +class WsInstance : public oatpp::websocket::AsyncWebSocket::Listener +{ +public: + WsInstance(const std::shared_ptr &socket, + const std::shared_ptr &hub, + const oatpp::String &connection_name, + v_int32 userId); + + // ---------------------------------------------------------------------- + // The WsInstance methods + // --------------------------------------------------------------------- + + /** + * Send message to WsInstance (to user). + * @param message + */ + void sendMessage(const oatpp::String &message); + + void sendBinaryMessage(const void *binary_message, int size); + + /** + * Get hub of the WsInstance. + * @return + */ + std::shared_ptr getHub(); + + /** + * Get WsInstance connection_name. + * @return + */ + oatpp::String getName(); + + /** + * Get WsInstance userId. + * @return + */ + v_int32 getId(); + +public: // WebSocket Listener methods + CoroutineStarter onPing(const std::shared_ptr &socket, const oatpp::String &message) override; + CoroutineStarter onPong(const std::shared_ptr &socket, const oatpp::String &message) override; + CoroutineStarter onClose(const std::shared_ptr &socket, v_uint16 code, const oatpp::String &message) override; + CoroutineStarter readMessage(const std::shared_ptr &socket, v_uint8 opcode, p_char8 data, oatpp::v_io_size size) override; + +private: + /** + * Buffer for messages. Needed for multi-frame messages. + */ + oatpp::data::stream::BufferOutputStream m_messageBuffer; + + /** + * Lock for synchronization of writes to the web socket. + */ + oatpp::async::Lock m_writeLock; + + + +private: + /** + * Inject async executor object. + */ + OATPP_COMPONENT(std::shared_ptr, m_asyncExecutor); + + std::shared_ptr m_socket; + std::shared_ptr m_hub; + oatpp::String m_connection_name; + v_int32 m_userId; + + std::shared_ptr> m_CommandDispatcher; + + std::shared_ptr m_SerializationEngine; + + std::shared_ptr m_DeserializationEngine; +}; + +#endif // WsINSTANCE_HPP diff --git a/src/websocket/WsProcessComponent.cpp b/src/websocket/WsProcessComponent.cpp deleted file mode 100644 index 087881e7..00000000 --- a/src/websocket/WsProcessComponent.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - * WsProcessComponent.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: Process API of WebSocket Server - -**************************************************/ - -#include "WebSocketServer.hpp" -#include "LithiumApp.hpp" - -#include "atom/utils/time.hpp" -#include "websocket/template/error_message.hpp" -#include "atom/error/error_code.hpp" - -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" -#include "magic_enum/magic_enum.hpp" - -void WebSocketServer::CreateProcessLi(const json &m_params) -{ - json res = {{"command", __func__}}; - if (!m_params.contains("command") || !m_params.contains("cmd_id")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Command and ID are required"); - } - try - { - if (!Lithium::MyApp->createProcess(m_params["command"].get(), m_params["cmd_id"].get())) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to create process"); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::RunScript(const json &m_params) -{ - json res = {{"command", __func__}}; - if (!m_params.contains("script_name") || !m_params.contains("script_id")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Script name and ID are required"); - } - try - { - if (!Lithium::MyApp->runScript(m_params["script_name"].get(), m_params["script_id"].get())) - { - RESPONSE_ERROR(res, ServerError::RunFailed, fmt::format("Failed to script")); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::TerminateProcessByName(const json &m_params) -{ - json res = {{"command", __func__}}; - if (!m_params.contains("process_name")) - { - RESPONSE_ERROR(res,ServerError::MissingParameters,"Process name is required"); - } - try - { - - std::string process_name = m_params["process_name"].get(); - if (!Lithium::MyApp->terminateProcessByName(process_name)) - { - RESPONSE_ERROR(res,ServerError::RunFailed,"Failed to terminate process"); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::GetRunningProcesses(const json &m_params) -{ - json res = {{"command", __func__}}; - try - { - for (auto process : Lithium::MyApp->getRunningProcesses()) - { - res["result"][process.name]["name"] = process.name; - res["result"][process.name]["pid"] = process.pid; - res["result"][process.name]["output"] = process.output; - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::GetProcessOutput(const json &m_params) -{ - json res = {{"command", __func__}}; - if (!m_params.contains("process_name") || !m_params.contains("cmd_id")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Process name is required"); - } - try - { - for (auto output : Lithium::MyApp->getProcessOutput(m_params["process_name"].get())) - { - res["result"].push_back(output); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} diff --git a/src/websocket/WsScriptComponent.cpp b/src/websocket/WsScriptComponent.cpp deleted file mode 100644 index 6dcee991..00000000 --- a/src/websocket/WsScriptComponent.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/* - * WsScriptComponent.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-8-10 - -Description: Script API of WebSocket Server - -**************************************************/ - -#include "WebSocketServer.hpp" -#include "LithiumApp.hpp" - -#include "atom/utils/time.hpp" -#include "websocket/template/error_message.hpp" -#include "atom/error/error_code.hpp" -#include "websocket/template/function.hpp" - -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" -#include "magic_enum/magic_enum.hpp" - -void WebSocketServer::runChaiCommand(const json &m_params) -{ - FUNCTION_BEGIN; - if (!m_params.contains("command")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "command content is required"); - } - std::string command = m_params["command"].get(); - if (!Lithium::MyApp->runChaiCommand(command)) - { - res["error"] = "ScriptError"; - res["message"] = "Failed to run command"; - } - FUNCTION_END; -} - -void WebSocketServer::runChaiMultiCommand(const json &m_params) -{ - FUNCTION_BEGIN; - if (!m_params.contains("command")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "command content is required"); - } - if (!Lithium::MyApp->runChaiMultiCommand(m_params["command"].get>())) - { - res["error"] = "ScriptError"; - res["message"] = "Failed to run multiline command"; - } - FUNCTION_END; -} - -void WebSocketServer::runChaiScript(const json &m_params) -{ - FUNCTION_BEGIN; - if (!m_params.contains("script")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "script name is required"); - } - if (!Lithium::MyApp->runChaiScript(m_params["script"].get())) - { - res["error"] = "ScriptError"; - res["message"] = "Failed to run script"; - } - FUNCTION_END; -} - -void WebSocketServer::loadChaiFile(const json &m_params) -{ - FUNCTION_BEGIN; - if (!m_params.contains("command")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "script name is required"); - } - if (!Lithium::MyApp->loadChaiScriptFile(m_params["script"].get())) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to load script"); - } - FUNCTION_END; -} - -void WebSocketServer::unloadChaiFile(const json &m_params) -{ - FUNCTION_BEGIN; - if (!m_params.contains("command")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "script name is required"); - } - if (!Lithium::MyApp->unloadChaiScriptFile(m_params["script"].get())) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to unload script"); - } - FUNCTION_END; -} diff --git a/src/websocket/WsServer.cpp b/src/websocket/WsServer.cpp new file mode 100644 index 00000000..7b84723f --- /dev/null +++ b/src/websocket/WsServer.cpp @@ -0,0 +1,70 @@ +/* + * WsServer.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-15 + +Description: WebSocket Server + +**************************************************/ + +#include "WsServer.hpp" + +v_int32 WsServer::obtainNewConnectionId() +{ + return m_ConnectionCounter++; +} + +std::shared_ptr WsServer::getOrCreateHub(const oatpp::String &hubName) +{ + std::lock_guard lock(m_hubsMutex); + std::shared_ptr &hub = m_hubs[hubName]; + if (!hub) + { + hub = std::make_shared(hubName); + } + return hub; +} + +void WsServer::onAfterCreate_NonBlocking(const std::shared_ptr &socket, const std::shared_ptr ¶ms) +{ + auto pluginName = params->find("pluginName")->second; + auto pluginHub = params->find("pluginHub")->second; + auto hub = getOrCreateHub(pluginHub); + + auto plugin = std::make_shared(socket, hub, pluginName, obtainNewConnectionId()); + socket->setListener(plugin); + + hub->addConnection(plugin); + hub->sendMessage(pluginName + " joined " + pluginHub); +} + +void WsServer::onBeforeDestroy_NonBlocking(const std::shared_ptr &socket) +{ + auto plugin = std::static_pointer_cast(socket->getListener()); + auto hub = plugin->getHub(); + hub->removeConnectionByUserId(plugin->getId()); + /* Remove circle `std::shared_ptr` dependencies */ + socket->setListener(nullptr); +} \ No newline at end of file diff --git a/src/websocket/WsServer.hpp b/src/websocket/WsServer.hpp new file mode 100644 index 00000000..70ab001f --- /dev/null +++ b/src/websocket/WsServer.hpp @@ -0,0 +1,119 @@ +/* + * WsServer.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-15 + +Description: WebSocket Server + +**************************************************/ + +#ifndef WSSERVER_HPP +#define WSSERVER_HPP + +#include "WsHub.hpp" + +#include "config.h" + +#if ENABLE_ASYNC +#include "oatpp-websocket/AsyncConnectionHandler.hpp" +#else +#include "oatpp-websocket/ConnectionHandler.hpp" +#endif + +#if ENABLE_FASTHASH +#include "emhash/hash_table8.hpp" +#else +#include +#endif +#include + +#include "atom/server/serialize.hpp" +#include "atom/server/deserialize.hpp" + +class WsServer : public oatpp::websocket::AsyncConnectionHandler::SocketInstanceListener +{ +public: + std::atomic m_ConnectionCounter; + std::unordered_map> m_hubs; + std::mutex m_hubsMutex; + +public: + WsServer() + : m_ConnectionCounter(0) + { + } + + +public: + /** + * Called when socket is created + */ + void onAfterCreate_NonBlocking(const std::shared_ptr &socket, const std::shared_ptr ¶ms) override; + + /** + * Called before socket instance is destroyed. + */ + void onBeforeDestroy_NonBlocking(const std::shared_ptr &socket) override; + + /** + * Generate id for new connection. + * @return + */ + v_int32 obtainNewConnectionId(); + + /** + * Get plugin hub by name or create new one if not exists. + * @param hubName + * @return + */ + std::shared_ptr getOrCreateHub(const oatpp::String &hubName); + +private: + + std::shared_ptr> m_CommandDispatcher; + + std::shared_ptr m_SerializationEngine; + + std::shared_ptr m_DeserializationEngine; + + template + void LiRegisterMemberFunc(const std::string &name, void (T::*memberFunc)(const json &)) + { + m_CommandDispatcher->RegisterMemberHandler(name, this, memberFunc); + } + + bool LiRunFunc(const std::string &name, const nlohmann::json ¶ms) + { + if (m_CommandDispatcher->HasHandler(name)) + { + m_CommandDispatcher->Dispatch(name, params); + return true; + } + return false; + } + +}; + +#endif // WSSERVER_HPP diff --git a/src/websocket/WsTaskComponent.cpp b/src/websocket/WsTaskComponent.cpp deleted file mode 100644 index fb24f820..00000000 --- a/src/websocket/WsTaskComponent.cpp +++ /dev/null @@ -1,305 +0,0 @@ -/* - * WsTaskComponent.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-7-13 - -Description: Task API of WebSocket Server - -**************************************************/ - -#include "WebSocketServer.hpp" -#include "LithiumApp.hpp" - -#include "atom/utils/time.hpp" -#include "websocket/template/error_message.hpp" -#include "atom/error/error_code.hpp" - -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" -#include "magic_enum/magic_enum.hpp" - -void WebSocketServer::AddTask(const json &m_params) -{ - json res = {{"command", __func__}}; - - // 检查必要参数是否存在 - if (!m_params.contains("device_name") && !m_params.contains("device_uuid")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Device name or uuid is required"); - } - // 检查任务的来源是否指定,主要是来自设备管理器和插件管理器 - if (!m_params.contains("task_origin") || !m_params.contains("task_name")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Task origin and name are required"); - } - - try - { - const std::string device_name = m_params.value("device_name", ""); - const std::string device_uuid = m_params.value("device_uuid", ""); - const std::string task_origin = m_params.value("task_origin", ""); - const std::string task_name = m_params.value("task_name", ""); - if (task_origin == "device") - { - DeviceType device_type; - auto it = DeviceTypeMap.find(m_params["device_type"]); - if (it == DeviceTypeMap.end()) - { - RESPONSE_ERROR(res, ServerError::InvalidParameters, "Unsupport device type"); - } - device_type = it->second; - std::shared_ptr task = Lithium::MyApp->getTask(device_type, device_name, task_name, m_params["task_params"]); - if (!task) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to add device task"); - } - if (!Lithium::MyApp->addTask(task)) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to add task to task manager"); - } - } - else if (task_origin == "plugin") - { - } - else - { - RESPONSE_ERROR(res, ServerError::InvalidFormat, "Unknown task origin"); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::InsertTask(const json &m_params) -{ - json res = {{"command", __func__}}; - try - { - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::ExecuteAllTasks(const json &m_params) -{ - json res = {{"command", __func__}}; - - try - { - if (!Lithium::MyApp->executeAllTasks()) - { - res = {{"error", "Task Failed"}, {"message", "Failed to execute task in sequence"}}; - LOG_F(ERROR, "WebSocketServer::ExecuteAllTasks : Failed to start executing all tasks"); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::StopTask(const json &m_params) -{ - json res = {{"command", __func__}}; - - try - { - if (!Lithium::MyApp->stopTask()) - { - res = {{"error", "Task Failed"}, {"message", "Failed to stop current task"}}; - LOG_F(ERROR, "WebSocketServer::StopTask(): Failed to stop current task"); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::ExecuteTaskByName(const json &m_params) -{ - json res = {{"command", __func__}}; - - if (!m_params.contains("task_name")) - { - RESPONSE_ERROR(res, ServerError::MissingParameters, "Task name is required"); - } - - try - { - const std::string task_name = m_params.value("task_name", ""); - if (!Lithium::MyApp->executeTaskByName(task_name)) - { - res = {{"error", "Task Failed"}, {"message", "Failed to execute specific task"}}; - LOG_F(ERROR, "WebSocketServer::ExecuteTaskByName(): Failed to execute specific task"); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::ModifyTask(const json &m_params) -{ - json res = {{"command", __func__}}; - try - { - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::ModifyTaskByName(const json &m_params) -{ - json res = {{"command", __func__}}; - try - { - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::DeleteTask(const json &m_params) -{ - json res = {{"command", __func__}}; - try - { - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::DeleteTaskByName(const json &m_params) -{ - json res = {{"command", __func__}}; - try - { - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::QueryTaskByName(const json &m_params) -{ - json res = {{"command", __func__}}; - try - { - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::GetTaskList(const json &m_params) -{ - json res = {{"command", __func__}}; - try - { - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} - -void WebSocketServer::SaveTasksToJson(const json &m_params) -{ - json res = {{"command", __func__}}; - - try - { - if (!Lithium::MyApp->saveTasksToJson()) - { - res = {{"error", "Task Failed"}, {"message", "Failed to save task in sequence to a JSON file"}}; - LOG_F(ERROR, "WebSocketServer::SaveTasksToJson(): Failed to save task in sequence to a JSON file"); - } - } - catch (const json::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::InvalidParameters, e.what()); - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION(res, ServerError::UnknownError, e.what()); - } -} \ No newline at end of file diff --git a/src/websocket/components/WsDeviceComponent.cpp b/src/websocket/components/WsDeviceComponent.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/websocket/components/WsDeviceComponent.hpp b/src/websocket/components/WsDeviceComponent.hpp new file mode 100644 index 00000000..e69de29b diff --git a/src/websocket/components/WsPluginComponent.cpp b/src/websocket/components/WsPluginComponent.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/websocket/components/WsPluginComponent.hpp b/src/websocket/components/WsPluginComponent.hpp new file mode 100644 index 00000000..e69de29b diff --git a/src/websocket/components/WsScriptComponent.cpp b/src/websocket/components/WsScriptComponent.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/websocket/components/WsScriptComponent.hpp b/src/websocket/components/WsScriptComponent.hpp new file mode 100644 index 00000000..e69de29b diff --git a/src/websocket/components/WsTaskComponent.cpp b/src/websocket/components/WsTaskComponent.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/websocket/components/WsTaskComponent.hpp b/src/websocket/components/WsTaskComponent.hpp new file mode 100644 index 00000000..e69de29b diff --git a/src/websocket/device/WsCameraInstance.cpp b/src/websocket/device/WsCameraInstance.cpp deleted file mode 100644 index 0893eec5..00000000 --- a/src/websocket/device/WsCameraInstance.cpp +++ /dev/null @@ -1,310 +0,0 @@ -/* - * WsCameraInstance.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Instance (each device each instance) - -**************************************************/ - -#include "WsCameraInstance.hpp" -#include "WsDeviceHub.hpp" - -#include "device/device_manager.hpp" -#include "atom/server/serialize.hpp" -#include "atom/server/deserialize.hpp" - -#include "atom/utils/time.hpp" -#include "websocket/template/error_message.hpp" -#include "websocket/template/function.hpp" -#include "atom/error/error_code.hpp" -#include "atom/utils/switch.hpp" - -#include "core/camera.hpp" - -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" -#include "magic_enum/magic_enum.hpp" - -WsCameraInstance::WsCameraInstance(const std::shared_ptr &socket, - const std::shared_ptr &hub, - const oatpp::String &device_name, - v_int32 userId) - : WsDeviceInstance(socket, hub, device_name, userId) -{ - LiRegisterFunc("startExopsure", &WsCameraInstance::startExposure, this); - LiRegisterFunc("stopExposure", &WsCameraInstance::stopExposure, this); - LiRegisterFunc("getExposureStatus", &WsCameraInstance::getExposureStatus, this); - LiRegisterFunc("getExposureResult", &WsCameraInstance::getExposureResult, this); - - LiRegisterFunc("startCooling", &WsCameraInstance::startCooling, this); - LiRegisterFunc("stopCooling", &WsCameraInstance::stopCooling, this); - LiRegisterFunc("getCoolingStatus", &WsCameraInstance::getCoolingStatus, this); - LiRegisterFunc("getCurrentTemperautre", &WsCameraInstance::getCurrentTemperature, this); - - LiRegisterFunc("getGain", &WsCameraInstance::getGain, this); - LiRegisterFunc("setGain", &WsCameraInstance::setGain, this); - LiRegisterFunc("getOffset", &WsCameraInstance::getOffset, this); - LiRegisterFunc("setOffset", &WsCameraInstance::setOffset, this); - LiRegisterFunc("getISO", &WsCameraInstance::getISO, this); - LiRegisterFunc("setISO", &WsCameraInstance::setISO, this); -} - -WsCameraInstance::~WsCameraInstance() -{ - DLOG_F(INFO, "WsCameraInstance Destroyed"); -} - -// ---------------------------------------------------------------- -// Exposure Functions -// ---------------------------------------------------------------- - -void WsCameraInstance::startExposure(const json &m_params) -{ - FUNCTION_BEGIN; - if (!m_params.contains("exposure") || !m_params["exposure"].is_number()) - { - RESPONSE_ERROR(res, ServerError::InvalidParameters, "Exposure time is required and must in number"); - } - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - if (!m_camera->startExposure(m_params)) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to start exposure"); - } - } - FUNCTION_END; -} - -void WsCameraInstance::stopExposure(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - if (!m_camera->getExposureStatus({})) - { - RESPONSE_ERROR(res, DeviceWarning::ExposureWarning, "Exposure is not running"); - } - if (!m_camera->abortExposure(m_params)) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to abort exposure"); - } - } - FUNCTION_END; -} - -void WsCameraInstance::getExposureStatus(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - } - FUNCTION_END; -} - -void WsCameraInstance::getExposureResult(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - } - FUNCTION_END; -} - -// ---------------------------------------------------------------- -// Cooling Functions -// ---------------------------------------------------------------- - -void WsCameraInstance::startCooling(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - if (!m_camera->startCooling(m_params)) - { - RESPONSE_ERROR(res, DeviceError::CoolingError, "Failed to start cooling"); - } - } - FUNCTION_END; -} - -void WsCameraInstance::stopCooling(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - if (!m_camera->getCoolingStatus(m_params)) - { - RESPONSE_ERROR(res, DeviceWarning::CoolingWarning, "Cooling mode is not started"); - } - if (!m_camera->stopCooling(m_params)) - { - RESPONSE_ERROR(res, DeviceError::CoolingError, "Failed to stop cooling"); - } - } - FUNCTION_END; -} - -void WsCameraInstance::getCoolingStatus(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - if (!m_camera->getCoolingStatus(m_params)) - { - RESPONSE_ERROR(res, DeviceWarning::CoolingWarning, "Cooling mode is not started"); - } - } - FUNCTION_END; -} - -void WsCameraInstance::getCurrentTemperature(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - if (!m_camera->getTemperature(m_params)) - { - RESPONSE_ERROR(res, DeviceError::CoolingError, "Failed to get temperature"); - } - } - FUNCTION_END; -} - -// ---------------------------------------------------------------- -// The following get property functions will not return the result directly, but via MessageBus in async mode -// ---------------------------------------------------------------- - -// ---------------------------------------------------------------- -// Commonly used -// ---------------------------------------------------------------- - -void WsCameraInstance::getGain(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - if (!m_camera->getGain(m_params)) - { - RESPONSE_ERROR(res, DeviceError::GainError, "Failed to get gain"); - } - } - FUNCTION_END; -} - -void WsCameraInstance::setGain(const json &m_params) -{ - FUNCTION_BEGIN; - if (!m_params.contains("gain") || !m_params["gain"].is_number()) - { - RESPONSE_ERROR(res, ServerError::InvalidParameters, "Gain is required and must in number"); - } - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - if (!m_camera->setGain(m_params)) - { - RESPONSE_ERROR(res, DeviceError::GainError, "Failed to set gain"); - } - } - FUNCTION_END; -} - -void WsCameraInstance::getOffset(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - if (!m_camera->getOffset(m_params)) - { - RESPONSE_ERROR(res, DeviceError::OffsetError, "Failed to get offset"); - } - } - FUNCTION_END; -} - -void WsCameraInstance::setOffset(const json &m_params) -{ - FUNCTION_BEGIN; - if (!m_params.contains("offset") || !m_params["offset"].is_number()) - { - RESPONSE_ERROR(res, ServerError::InvalidParameters, "Offset is required and must in number"); - } - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - if (!m_camera->setOffset(m_params)) - { - RESPONSE_ERROR(res, DeviceError::OffsetError, "Failed to set offset"); - } - } - FUNCTION_END; -} - -void WsCameraInstance::getISO(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - if (!m_camera->getISO(m_params)) - { - RESPONSE_ERROR(res, DeviceError::OffsetError, "Failed to get ISO value"); - } - } - FUNCTION_END; -} - -void WsCameraInstance::setISO(const json &m_params) -{ - FUNCTION_BEGIN; - if (!m_params.contains("iso") || !m_params["iso"].is_number()) - { - RESPONSE_ERROR(res, ServerError::InvalidParameters, "ISO is required and must in number"); - } - CHECK_DEVICE_VALIDITY(m_camera, Camera) - else - { - if (!m_camera->isISOAvailable()) - { - RESPONSE_ERROR(res, ServerError::InvalidParameters, "ISO is not supported"); - } - if (!m_camera->setOffset(m_params)) - { - RESPONSE_ERROR(res, DeviceError::ISOError, "Failed to set ISO"); - } - } - FUNCTION_END; -} diff --git a/src/websocket/device/WsCameraInstance.hpp b/src/websocket/device/WsCameraInstance.hpp deleted file mode 100644 index 1032301f..00000000 --- a/src/websocket/device/WsCameraInstance.hpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * WsCameraInstance.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Instance (each device each instance) - -**************************************************/ - -#ifndef WSCAMERAINSTANCE_HPP -#define WSCAMERAINSTANCE_HPP - -#include "WsDeviceInstance.hpp" - -class Camera; - -template -class StringSwitch; - -/** - * @brief Class representing an instance of a WebSocket Camera - * - */ -class WsCameraInstance : public WsDeviceInstance -{ - -public: - /** - * @brief Construct a new WsCameraInstance object. - * - * @param socket Shared pointer to the AsyncWebSocket object. - * @param hub Shared pointer to the WsDeviceHub object. - * @param device_name Name of the device. - * @param userId Id of the user. - */ - WsCameraInstance(const std::shared_ptr &socket, - const std::shared_ptr &hub, - const oatpp::String &device_name, - v_int32 userId); - - /** - * @brief Destroy the WsCameraInstance object. - * - */ - ~WsCameraInstance(); - -public: - - // ---------------------------------------------------------------- - // Exposure Functions - // ---------------------------------------------------------------- - - void startExposure(const json &m_params); - void stopExposure(const json &m_parmas); - void getExposureStatus(const json &m_params); - void getExposureResult(const json &m_params); - - // ---------------------------------------------------------------- - // Cooling Functions - // ---------------------------------------------------------------- - - void startCooling(const json &m_params); - void stopCooling(const json &m_params); - void getCoolingStatus(const json &m_params); - void getCurrentTemperature(const json &m_params); - - // ---------------------------------------------------------------- - // Camera Property Functions (Directly) - // ---------------------------------------------------------------- - - void getGain(const json &m_params); - void setGain(const json &m_params); - void setOffset(const json &m_params); - void getOffset(const json &m_params); - void getISO(const json &m_params); - void setISO(const json &m_params); - -private: - - std::shared_ptr m_camera; - std::shared_ptr> m_property_type_switch; -}; - -#endif // WSCAMERAINSTANCE_HPP diff --git a/src/websocket/device/WsDeviceHub.cpp b/src/websocket/device/WsDeviceHub.cpp deleted file mode 100644 index 3ea7be5e..00000000 --- a/src/websocket/device/WsDeviceHub.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * WsDeviceHub.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Hub (all devices of one type in one hub) - -**************************************************/ - -#include "WsDeviceHub.hpp" - -void WsDeviceHub::addDevice(const std::shared_ptr &device) -{ - std::lock_guard guard(m_deviceByIdLock); - m_deviceById[device->getUserId()] = device; -} - -void WsDeviceHub::removedeviceByUserId(v_int32 userId) -{ - std::lock_guard guard(m_deviceByIdLock); - m_deviceById.erase(userId); -} - -void WsDeviceHub::sendMessage(const oatpp::String &message) -{ - std::lock_guard guard(m_deviceByIdLock); - for (auto &pair : m_deviceById) - { - pair.second->sendMessage(message); - } -} - -void WsDeviceHub::sendBinaryMessage(void *binary_message, int size) -{ - std::lock_guard guard(m_deviceByIdLock); - for (auto &pair : m_deviceById) - { - pair.second->sendBinaryMessage(binary_message, size); - } -} \ No newline at end of file diff --git a/src/websocket/device/WsDeviceHub.hpp b/src/websocket/device/WsDeviceHub.hpp deleted file mode 100644 index 1cb8c6f8..00000000 --- a/src/websocket/device/WsDeviceHub.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * WsDeviceHub.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Hub (all devices of one type in one hub) - -**************************************************/ - -#ifndef WSDEVICEHUB_HPP -#define WSDEVICEHUB_HPP - -#include "WsDeviceInstance.hpp" - -#if ENABLE_FASTHASH -#include "emhash/hash_table8.hpp" -#else -#include -#endif - -class WsDeviceHub -{ -private: - oatpp::String m_name; - std::unordered_map> m_deviceById; - std::mutex m_deviceByIdLock; - -public: - WsDeviceHub(const oatpp::String &name) - : m_name(name) - { - } - - /** - * Add device to the WsDeviceHub. - * @param device - */ - void addDevice(const std::shared_ptr &device); - - /** - * Remove device from the WsDeviceHub. - * @param userId - */ - void removedeviceByUserId(v_int32 userId); - - /** - * Send message to all devices in the WsDeviceHub. - * @param message - */ - void sendMessage(const oatpp::String &message); - - /** - * Send binary message to all devices in the WsDeviceHub. - * @param binary_message - * @param size - */ - void sendBinaryMessage(void *binary_message, int size); -}; - -#endif // WSDEVICEHUB_HPP diff --git a/src/websocket/device/WsDeviceInstance.cpp b/src/websocket/device/WsDeviceInstance.cpp deleted file mode 100644 index c560bced..00000000 --- a/src/websocket/device/WsDeviceInstance.cpp +++ /dev/null @@ -1,314 +0,0 @@ -/* - * WsDeviceInstance.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Instance (each device each instance) - -**************************************************/ - -#include "WsDeviceInstance.hpp" -#include "WsDeviceHub.hpp" - -#include "device/device_manager.hpp" -#include "atom/server/serialize.hpp" -#include "atom/server/deserialize.hpp" - -#include "atom/utils/time.hpp" -#include "websocket/template/error_message.hpp" -#include "websocket/template/function.hpp" -#include "websocket/template/variable.hpp" -#include "atom/error/error_code.hpp" - -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" -#include "magic_enum/magic_enum.hpp" - -WsDeviceInstance::WsDeviceInstance(const std::shared_ptr &socket, - const std::shared_ptr &hub, - const oatpp::String &device_name, - v_int32 userId) - : m_socket(socket), m_hub(hub), m_device_name(device_name), m_userId(userId) -{ - OATPP_LOGD(m_device_name.getValue("").c_str(), "%s created", m_device_name.getValue("").c_str()); - - m_CommandDispatcher = std::make_unique>(); - - LiRegisterFunc("getProperty", &WsDeviceInstance::getProperty, this); - LiRegisterFunc("getProperties", &WsDeviceInstance::getProperties, this); - LiRegisterFunc("setProperty", &WsDeviceInstance::setProperty, this); - LiRegisterFunc("runTask", &WsDeviceInstance::runTask, this); - LiRegisterFunc("runFunc", &WsDeviceInstance::runFunc, this); - LiRegisterFunc("loadDriverLibrary", &WsDeviceInstance::loadDriverLibrary, this); - LiRegisterFunc("unloadDriverLibrary", &WsDeviceInstance::unloadDriverLibrary, this); - LiRegisterFunc("addDriver", &WsDeviceInstance::addDriver, this); - LiRegisterFunc("removeDriver", &WsDeviceInstance::removeDriver, this); - - m_SerializationEngine = std::make_unique(); - m_DeserializationEngine = std::make_unique(); -} - -WsDeviceInstance::~WsDeviceInstance() -{ -} - -void WsDeviceInstance::sendMessage(const oatpp::String &message) -{ - - class SendMessageCoroutine : public oatpp::async::Coroutine - { - private: - oatpp::async::Lock *m_lock; - std::shared_ptr m_websocket; - oatpp::String m_message; - - public: - SendMessageCoroutine(oatpp::async::Lock *lock, - const std::shared_ptr &websocket, - const oatpp::String &message) - : m_lock(lock), m_websocket(websocket), m_message(message) - { - } - - Action act() override - { - return oatpp::async::synchronize(m_lock, m_websocket->sendOneFrameTextAsync(m_message)).next(finish()); - } - }; - - m_asyncExecutor->execute(&m_writeLock, m_socket, message); -} - -void WsDeviceInstance::sendBinaryMessage(void *binary_message, int size) -{ - oatpp::String binary((const char *)binary_message, size); - class SendMessageCoroutine : public oatpp::async::Coroutine - { - private: - oatpp::async::Lock *m_lock; - std::shared_ptr m_websocket; - oatpp::String m_message; - - public: - SendMessageCoroutine(oatpp::async::Lock *lock, - const std::shared_ptr &websocket, - const oatpp::String &message) - : m_lock(lock), m_websocket(websocket), m_message(message) - { - } - - Action act() override - { - return oatpp::async::synchronize(m_lock, m_websocket->sendOneFrameTextAsync(m_message)).next(finish()); - } - }; - - m_asyncExecutor->execute(&m_writeLock, m_socket, binary); -} - -std::shared_ptr WsDeviceInstance::getHub() -{ - return m_hub; -} - -oatpp::String WsDeviceInstance::getDeviceName() -{ - return m_device_name; -} - -v_int32 WsDeviceInstance::getUserId() -{ - return m_userId; -} - -oatpp::async::CoroutineStarter WsDeviceInstance::onPing(const std::shared_ptr &socket, const oatpp::String &message) -{ - return oatpp::async::synchronize(&m_writeLock, socket->sendPongAsync(message)); -} - -oatpp::async::CoroutineStarter WsDeviceInstance::onPong(const std::shared_ptr &socket, const oatpp::String &message) -{ - return nullptr; // do nothing -} - -oatpp::async::CoroutineStarter WsDeviceInstance::onClose(const std::shared_ptr &socket, v_uint16 code, const oatpp::String &message) -{ - return nullptr; // do nothing -} - -oatpp::async::CoroutineStarter WsDeviceInstance::readMessage(const std::shared_ptr &socket, v_uint8 opcode, p_char8 data, oatpp::v_io_size size) -{ - - if (size == 0) - { // message transfer finished - auto wholeMessage = m_messageBuffer.toString(); - m_messageBuffer.setCurrentPosition(0); - - json res; - if (!json::accept(wholeMessage->c_str())) - { - RESPONSE_ERROR_C(res, ServerError::InvalidFormat, "Message is not in JSON format"); - } - else - { - try - { - json jdata = json::parse(wholeMessage->c_str()); - if (jdata.contains("name") && jdata.contains("params")) - { - const std::string name = jdata["name"].get(); - if (m_CommandDispatcher->HasHandler(name)) - { - m_CommandDispatcher->Dispatch(name, jdata["params"]); - } - } - else - { - RESPONSE_ERROR_C(res, ServerError::MissingParameters, "Missing parameter: name or params"); - } - } - catch (const std::exception &e) - { - RESPONSE_EXCEPTION_C(res, ServerError::UnknownError, e.what()); - } - } - if (res.contains("error") || res.contains("message")) - { - sendMessage(res.dump()); - } - } - else if (size > 0) - { // message frame received - m_messageBuffer.writeSimple(data, size); - } - return nullptr; // do nothing -} - -void WsDeviceInstance::loadDriverLibrary(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_PARAM_EXISTS(lib_path); - CHECK_PARAM_EXISTS(lib_name); - GET_STRING_PARAM_VALUE(m_params["lib_path"], lib_path); - GET_STRING_PARAM_VALUE(m_params["lib_name"], lib_name); - if (!Lithium::MyApp->addDeviceLibrary(lib_path, lib_name)) - { - res["error"] = "Failed to add device library"; - } - FUNCTION_END; -} - -void WsDeviceInstance::unloadDriverLibrary(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_PARAM_EXISTS(lib_name); - GET_STRING_PARAM_VALUE(m_params["lib_name"], lib_name); - if (!Lithium::MyApp->removeDeviceLibrary(lib_name)) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to remove device library"); - } - FUNCTION_END; -} - -void WsDeviceInstance::addDriver(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_PARAM_EXISTS(device_type); - CHECK_PARAM_EXISTS(device_name); - SET_DEVICE_TYPE(m_params["device_type"]); - GET_STRING_PARAM_VALUE(m_params["device_name"], device_name); - if (!Lithium::MyApp->addDevice(device_type, device_name, m_params.value("lib_name", ""))) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to add device"); - } - else - { - SET_STRING_PARAM_VALUE(device_name, m_device_name); - Lithium::MyApp->addDeviceObserver(device_type, device_name); - } - FUNCTION_END; -} - -void WsDeviceInstance::removeDriver(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_PARAM_EXISTS(device_type); - CHECK_PARAM_EXISTS(device_name); - SET_DEVICE_TYPE(m_params["device_type"]); - GET_STRING_PARAM_VALUE(m_params["device_name"], device_name); - if (!Lithium::MyApp->removeDevice(device_type, device_name)) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to remove device"); - } - FUNCTION_END; -} - -void WsDeviceInstance::setProperty(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_PARAM_EXISTS(name); - CHECK_PARAM_EXISTS(value); - GET_STRING_PARAM_VALUE(m_params["name"], name); - GET_STRING_PARAM_VALUE(m_params["value"], value); - if (!Lithium::MyApp->setProperty(m_device_name, name, value)) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to remove device library"); - } - FUNCTION_END; -} - -void WsDeviceInstance::getProperty(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_PARAM_EXISTS(name); - GET_STRING_PARAM_VALUE(m_params["name"], name); - if (!Lithium::MyApp->getProperty(m_device_name, name)) - { - RESPONSE_ERROR(res, ServerError::RunFailed, "Failed to get device property"); - } - FUNCTION_END; -} - -void WsDeviceInstance::getProperties(const json &m_params) -{ - FUNCTION_BEGIN; - FUNCTION_END; -} - -void WsDeviceInstance::runTask(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_PARAM_EXISTS(task_name); - GET_STRING_PARAM_VALUE(m_params["task_name"], task_name); - FUNCTION_END; -} - -void WsDeviceInstance::runFunc(const json &m_params) -{ - FUNCTION_BEGIN; - CHECK_PARAM_EXISTS(func_name); - GET_STRING_PARAM_VALUE(m_params["func_name"], func_name); - FUNCTION_END; -} \ No newline at end of file diff --git a/src/websocket/device/WsDeviceInstance.hpp b/src/websocket/device/WsDeviceInstance.hpp deleted file mode 100644 index 0cc0a884..00000000 --- a/src/websocket/device/WsDeviceInstance.hpp +++ /dev/null @@ -1,304 +0,0 @@ -/* - * WsDeviceInstance.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Instance (each device each instance) - -**************************************************/ - -#ifndef WSDEVICEINSTANCE_HPP -#define WSDEVICEINSTANCE_HPP - -#include "config.h" - -#if ENABLE_ASYNC -#include "oatpp-websocket/AsyncWebSocket.hpp" -#else -#include "oatpp-websocket/WebSocket.hpp" -#endif - -#if ENABLE_ASYNC -#include "oatpp/core/async/Lock.hpp" -#include "oatpp/core/async/Executor.hpp" -#endif - -#include "oatpp/core/macro/component.hpp" - -#include "atom/server/commander.hpp" - -#include "LithiumApp.hpp" - -#include - -#include "atom/type/json.hpp" - -using json = nlohmann::json; - -class WsDeviceHub; // FWD -class SerializationEngine; -class DeserializationEngine; - -#define CHECK_DEVICE_VALIDITY(device, deviceType) \ - if (!device) \ - { \ - if (!m_params.contains("device_name") || !m_params["device_name"].is_string()) \ - { \ - RESPONSE_ERROR(res, ServerError::InvalidParameters, "Device name is required"); \ - } \ - if (Lithium::MyApp->findDevice(DeviceType::deviceType, m_params["device_name"].get()) == -1) \ - { \ - RESPONSE_ERROR(res, ServerError::InvalidParameters, "Device not found"); \ - } \ - try \ - { \ - device = std::dynamic_pointer_cast(Lithium::MyApp->getDevice(DeviceType::deviceType, m_params["device_name"].get())); \ - } \ - catch (const std::bad_any_cast &e) \ - { \ - RESPONSE_ERROR(res, ServerError::UnknownError, fmt::format("{} with {}", "Failed to cast pointer", e.what())); \ - } \ - } - -#define SET_DEVICE_TYPE(device_type_) \ - DeviceType device_type; \ - auto it = DeviceTypeMap.find(device_type_); \ - if (it == DeviceTypeMap.end()) \ - { \ - RESPONSE_ERROR(res, ServerError::InvalidParameters, "Unsupport device type"); \ - } \ - device_type = it->second; - -/** - * @brief Class representing an instance of a WebSocket device. - * - */ -class WsDeviceInstance : public oatpp::websocket::AsyncWebSocket::Listener -{ - -public: - /** - * @brief Construct a new WsDeviceInstance object. - * - * @param socket Shared pointer to the AsyncWebSocket object. - * @param hub Shared pointer to the WsDeviceHub object. - * @param device_name Name of the device. - * @param userId Id of the user. - */ - WsDeviceInstance(const std::shared_ptr &socket, - const std::shared_ptr &hub, - const oatpp::String &device_name, - v_int32 userId); - - /** - * @brief Destroy the WsDeviceInstance object. - * - */ - ~WsDeviceInstance(); - - /** - * @brief Send a message to the WsDeviceInstance (to user). - * - * @param message The message to be sent. - */ - void sendMessage(const oatpp::String &message); - - /** - * @brief Send a binary message to the WsDeviceInstance (to user). - * - * @param binary_message Pointer to the binary message. - * @param size Size of the binary message. - */ - void sendBinaryMessage(void *binary_message, int size); - - /** - * @brief Get the hub of the WsDeviceInstance. - * - * @return Shared pointer to the WsDeviceHub object. - */ - std::shared_ptr getHub(); - - /** - * @brief Get the name of the WsDeviceInstance device. - * - * @return The name of the device. - */ - oatpp::String getDeviceName(); - - /** - * @brief Get the id of the WsDeviceInstance user. - * - * @return The id of the user. - */ - v_int32 getUserId(); - -public: - void loadDriverLibrary(const json &m_params); - - void unloadDriverLibrary(const json &m_params); - - void addDriver(const json &m_params); - - void removeDriver(const json &m_params); - /** - * @brief Set a property of the WsDeviceInstance. - * - * @param m_params JSON object containing the property to be set. - */ - void setProperty(const json &m_params); - - /** - * @brief Get a property of the WsDeviceInstance. - * - * @param m_params JSON object containing the property to be retrieved. - */ - void getProperty(const json &m_params); - - /** - * @brief Get all properties of the WsDeviceInstance. - * - * @param m_params JSON object containing the request. - */ - void getProperties(const json &m_params); - - /** - * @brief Run a task on the WsDeviceInstance. - * - * @param m_params JSON object containing the task to be run. - */ - void runTask(const json &m_params); - - /** - * @brief Run a function on the WsDeviceInstance. - * - * @param m_params JSON object containing the function to be run. - */ - void runFunc(const json &m_params); - -public: // WebSocket Listener methods - /** - * @brief Handle a ping message received by the WebSocket. - * - * @param socket Shared pointer to the AsyncWebSocket object. - * @param message The ping message received. - * @return CoroutineStarter object. - */ - CoroutineStarter onPing(const std::shared_ptr &socket, const oatpp::String &message) override; - - /** - * @brief Handle a pong message received by the WebSocket. - * - * @param socket Shared pointer to the AsyncWebSocket object. - * @param message The pong message received. - * @return CoroutineStarter object. - */ - CoroutineStarter onPong(const std::shared_ptr &socket, const oatpp::String &message) override; - - /** - * @brief Handle the WebSocket being closed. - * - * @param socket Shared pointer to the AsyncWebSocket object. - * @param code The close code. - * @param message The close message. - * @return CoroutineStarter object. - */ - CoroutineStarter onClose(const std::shared_ptr &socket, v_uint16 code, const oatpp::String &message) override; - - /** - * @brief Handle a message received by the WebSocket. - * - * @param socket Shared pointer to the AsyncWebSocket object. - * @param opcode The opcode of the message. - * @param data Pointer to the message data. - * @param size The size of the message data. - * @return CoroutineStarter object. - */ - CoroutineStarter readMessage(const std::shared_ptr &socket, v_uint8 opcode, p_char8 data, oatpp::v_io_size size) override; - -public: - /** - * @brief Register a function handler for the VCommandDispatcher. - * - * @tparam ClassType The class type of the handler. - * @param name The name of the function. - * @param handler The function handler. - */ - template - void LiRegisterFunc(const std::string &name, void (T::*memberFunc)(const json &), T *object) - { - m_CommandDispatcher->RegisterMemberHandler(name, object, memberFunc); - } - - /** - * @brief Run a function on the VCommandDispatcher. - * - * @param name The name of the function to be run. - * @param params JSON object containing the parameters for the function. - * @return True if the function was run successfully, false otherwise. - */ - bool LiRunFunc(const std::string &name, const json ¶ms); - -private: - /** - * @brief Buffer for messages. Needed for multi-frame messages. - * - */ - oatpp::data::stream::BufferOutputStream m_messageBuffer; - - /** - * @brief Lock for synchronization of writes to the WebSocket. - * - */ - oatpp::async::Lock m_writeLock; - - std::unique_ptr> m_CommandDispatcher; - - std::unique_ptr m_SerializationEngine; - - std::unique_ptr m_DeserializationEngine; - - std::unordered_map DeviceTypeMap = { - {"Camera", DeviceType::Camera}, - {"Telescope", DeviceType::Telescope}, - {"Focuser", DeviceType::Focuser}, - {"FilterWheel", DeviceType::FilterWheel}, - {"Solver", DeviceType::Solver}, - {"Guider", DeviceType::Guider}}; - -private: - std::shared_ptr m_socket; - std::shared_ptr m_hub; - oatpp::String m_device_name; - v_int32 m_userId; - -private: - /** - * @brief Inject async executor object. - * - */ - OATPP_COMPONENT(std::shared_ptr, m_asyncExecutor); -}; - -#endif // WSDEVICEINSTANCE_HPP diff --git a/src/websocket/device/WsDeviceServer.cpp b/src/websocket/device/WsDeviceServer.cpp deleted file mode 100644 index 3f597af8..00000000 --- a/src/websocket/device/WsDeviceServer.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * WsDeviceServer.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Server - -**************************************************/ - -#include "WsDeviceServer.hpp" - -#include "WsCameraInstance.hpp" -#include "WsFilterInstance.hpp" -#include "WsFocuserInstance.hpp" -#include "WsTelescopeInstance.hpp" - -WsDeviceServer::WsDeviceServer() : m_userIdCounter(0) -{ - m_device_switch = std::make_unique &, const oatpp::String &, const oatpp::String &>>(); - m_device_switch->registerCase("camera", [this](const std::shared_ptr &socket, const oatpp::String &deviceName, const oatpp::String &deviceHub) - { - auto hub = getOrCreateHub(deviceHub); - auto device = std::make_shared(socket, hub, deviceName, obtainNewUserId()); - socket->setListener(device); - hub->addDevice(device); }); - m_device_switch->registerCase("telescope", [this](const std::shared_ptr &socket, const oatpp::String &deviceName, const oatpp::String &deviceHub) - { - auto hub = getOrCreateHub(deviceHub); - auto device = std::make_shared(socket, hub, deviceName, obtainNewUserId()); - socket->setListener(device); - hub->addDevice(device); }); - m_device_switch->registerCase("focuser", [this](const std::shared_ptr &socket, const oatpp::String &deviceName, const oatpp::String &deviceHub) - { - auto hub = getOrCreateHub(deviceHub); - auto device = std::make_shared(socket, hub, deviceName, obtainNewUserId()); - socket->setListener(device); - hub->addDevice(device); }); - m_device_switch->registerCase("filterwheel", [this](const std::shared_ptr &socket, const oatpp::String &deviceName, const oatpp::String &deviceHub) - { - auto hub = getOrCreateHub(deviceHub); - auto device = std::make_shared(socket, hub, deviceName, obtainNewUserId()); - socket->setListener(device); - hub->addDevice(device); }); - m_device_switch->setDefault([this](const std::shared_ptr &socket, const oatpp::String &deviceName, const oatpp::String &deviceHub) - { - auto hub = getOrCreateHub(deviceHub); - auto device = std::make_shared(socket, hub, deviceName, obtainNewUserId()); - socket->setListener(device); - hub->addDevice(device); }); -} -v_int32 WsDeviceServer::obtainNewUserId() -{ - return m_userIdCounter++; -} - -std::shared_ptr WsDeviceServer::getOrCreateHub(const oatpp::String &hubName) -{ - std::lock_guard lock(m_hubsMutex); - std::shared_ptr &hub = m_hubs[hubName]; - if (!hub) - { - hub = std::make_shared(hubName); - } - return hub; -} - -void WsDeviceServer::onAfterCreate_NonBlocking(const std::shared_ptr &socket, const std::shared_ptr ¶ms) -{ - auto deviceName = params->find("deviceName")->second; - auto deviceHub = params->find("deviceHub")->second; - auto deviceType = params->find("deviceType")->second; - m_device_switch->match(deviceType, socket, deviceName, deviceHub); -} - -void WsDeviceServer::onBeforeDestroy_NonBlocking(const std::shared_ptr &socket) -{ - - auto device = std::static_pointer_cast(socket->getListener()); - auto device_name = device->getDeviceName(); - auto hub = device->getHub(); - - hub->removedeviceByUserId(device->getUserId()); - - hub->sendMessage(device_name + " left the hub"); - - /* Remove circle `std::shared_ptr` dependencies */ - socket->setListener(nullptr); -} \ No newline at end of file diff --git a/src/websocket/device/WsDeviceServer.hpp b/src/websocket/device/WsDeviceServer.hpp deleted file mode 100644 index 2df96ee7..00000000 --- a/src/websocket/device/WsDeviceServer.hpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * WsDeviceServer.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Server - -**************************************************/ - -#ifndef WSDEVICESERVER_HPP -#define WSDEVICESERVER_HPP - -#include "WsDeviceHub.hpp" - -#include "config.h" - -#include - -#if ENABLE_ASYNC -#include "oatpp-websocket/AsyncConnectionHandler.hpp" -#else -#include "oatpp-websocket/ConnectionHandler.hpp" -#endif - -#if ENABLE_FASTHASH -#include "emhash/hash_table8.hpp" -#else -#include -#endif - -#include "atom/utils/switch.hpp" - -#if ENABLE_ASYNC -class WsDeviceServer : public oatpp::websocket::AsyncConnectionHandler::SocketInstanceListener -#else -class WsDeviceServer : public oatpp::websocket::ConnectionHandler::SocketInstanceListener -#endif -{ -public: - WsDeviceServer(); - - /** - * Generate id for new user - * @return - */ - v_int32 obtainNewUserId(); - - /** - * Get device hub by name or create new one if not exists. - * @param hubName - * @return - */ - std::shared_ptr getOrCreateHub(const oatpp::String &hubName); - -public: -#if ENABLE_ASYNC - /** - * @brief Callback function called after creating a new WebSocket connection in non-blocking mode. - * @param socket The newly created WebSocket connection. - * @param params The parameters associated with the connection. - */ - void onAfterCreate_NonBlocking(const std::shared_ptr &socket, const std::shared_ptr ¶ms) override; - - /** - * @brief Callback function called before destroying a WebSocket connection in non-blocking mode. - * @param socket The WebSocket connection to be destroyed. - */ - void onBeforeDestroy_NonBlocking(const std::shared_ptr &socket) override; -#else - /** - * @brief Callback function called after creating a new WebSocket connection. - * @param socket The newly created WebSocket connection. - * @param params The parameters associated with the connection. - */ - void onAfterCreate(const oatpp::websocket::WebSocket &socket, const std::shared_ptr ¶ms) override; - - /** - * @brief Callback function called before destroying a WebSocket connection. - * @param socket The WebSocket connection to be destroyed. - */ - void onBeforeDestroy(const oatpp::websocket::WebSocket &socket) override; -#endif - -public: - std::atomic m_userIdCounter; - std::unordered_map> m_hubs; - std::mutex m_hubsMutex; - std::unique_ptr &, const oatpp::String &, const oatpp::String &>> m_device_switch; -}; - -#endif // WSDEVICESERVER_HPP diff --git a/src/websocket/device/WsFilterInstance.cpp b/src/websocket/device/WsFilterInstance.cpp deleted file mode 100644 index 2b7fecb2..00000000 --- a/src/websocket/device/WsFilterInstance.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * WsFilterInstance.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Instance (each device each instance) - -**************************************************/ - -#include "WsFilterInstance.hpp" -#include "WsDeviceHub.hpp" - -#include "device/device_manager.hpp" -#include "atom/server/serialize.hpp" -#include "atom/server/deserialize.hpp" - -#include "atom/utils/time.hpp" -#include "websocket/template/error_message.hpp" -#include "atom/error/error_code.hpp" - -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" -#include "magic_enum/magic_enum.hpp" - -WsFilterInstance::WsFilterInstance(const std::shared_ptr &socket, - const std::shared_ptr &hub, - const oatpp::String &device_name, - v_int32 userId) - : WsDeviceInstance(socket, hub, device_name, userId) -{ -} - -WsFilterInstance::~WsFilterInstance() -{ -} diff --git a/src/websocket/device/WsFilterInstance.hpp b/src/websocket/device/WsFilterInstance.hpp deleted file mode 100644 index 2c0e4731..00000000 --- a/src/websocket/device/WsFilterInstance.hpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * WsFilterInstance.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Instance (each device each instance) - -**************************************************/ - -#ifndef WSFILTERINSTANCE_HPP -#define WSFILTERINSTANCE_HPP - -#include "WsDeviceInstance.hpp" - -/** - * @brief Class representing an instance of a WebSocket Filter - * - */ -class WsFilterInstance : public WsDeviceInstance -{ - -public: - /** - * @brief Construct a new WsFilterInstance object. - * - * @param socket Shared pointer to the AsyncWebSocket object. - * @param hub Shared pointer to the WsDeviceHub object. - * @param device_name Name of the device. - * @param userId Id of the user. - */ - WsFilterInstance(const std::shared_ptr &socket, - const std::shared_ptr &hub, - const oatpp::String &device_name, - v_int32 userId); - - /** - * @brief Destroy the WsFilterInstance object. - * - */ - ~WsFilterInstance(); - -public: -}; - -#endif // WSFILTERINSTANCE_HPP diff --git a/src/websocket/device/WsFocuserInstance.cpp b/src/websocket/device/WsFocuserInstance.cpp deleted file mode 100644 index 6190e010..00000000 --- a/src/websocket/device/WsFocuserInstance.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * WsFocuserInstance.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Instance (each device each instance) - -**************************************************/ - -#include "WsFocuserInstance.hpp" -#include "WsDeviceHub.hpp" - -#include "device/device_manager.hpp" -#include "atom/server/serialize.hpp" -#include "atom/server/deserialize.hpp" - -#include "atom/utils/time.hpp" -#include "websocket/template/error_message.hpp" -#include "atom/error/error_code.hpp" - -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" -#include "magic_enum/magic_enum.hpp" - -WsFocuserInstance::WsFocuserInstance(const std::shared_ptr &socket, - const std::shared_ptr &hub, - const oatpp::String &device_name, - v_int32 userId) - : WsDeviceInstance(socket, hub, device_name, userId) -{ -} - -WsFocuserInstance::~WsFocuserInstance() -{ -} diff --git a/src/websocket/device/WsFocuserInstance.hpp b/src/websocket/device/WsFocuserInstance.hpp deleted file mode 100644 index 60939ddc..00000000 --- a/src/websocket/device/WsFocuserInstance.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * WsFocuserInstance.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Instance (each device each instance) - -**************************************************/ - -#ifndef WSFOCUSERINSTANCE_HPP -#define WSFOCUSERINSTANCE_HPP - -#include "WsDeviceInstance.hpp" - -/** - * @brief Class representing an instance of a WebSocket Focuser - * - */ -class WsFocuserInstance : public WsDeviceInstance -{ - -public: - /** - * @brief Construct a new WsFocuserInstance object. - * - * @param socket Shared pointer to the AsyncWebSocket object. - * @param hub Shared pointer to the WsDeviceHub object. - * @param device_name Name of the device. - * @param userId Id of the user. - */ - WsFocuserInstance(const std::shared_ptr &socket, - const std::shared_ptr &hub, - const oatpp::String &device_name, - v_int32 userId); - - /** - * @brief Destroy the WsFocuserInstance object. - * - */ - ~WsFocuserInstance(); - -public: - -}; - -#endif // WSFOCUSERINSTANCE_HPP diff --git a/src/websocket/device/WsTelescopeInstance.cpp b/src/websocket/device/WsTelescopeInstance.cpp deleted file mode 100644 index 8ed880f4..00000000 --- a/src/websocket/device/WsTelescopeInstance.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * WsTelescopeInstance.cpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Instance (each device each instance) - -**************************************************/ - -#include "WsTelescopeInstance.hpp" -#include "WsDeviceHub.hpp" - -#include "device/device_manager.hpp" -#include "atom/server/serialize.hpp" -#include "atom/server/deserialize.hpp" - -#include "atom/utils/time.hpp" -#include "websocket/template/error_message.hpp" -#include "atom/error/error_code.hpp" - -#include "atom/log/loguru.hpp" -#include "atom/type/json.hpp" -#include "magic_enum/magic_enum.hpp" - -WsTelescopeInstance::WsTelescopeInstance(const std::shared_ptr &socket, - const std::shared_ptr &hub, - const oatpp::String &device_name, - v_int32 userId) - : WsDeviceInstance(socket, hub, device_name, userId) -{ - LiRegisterFunc("startExopsure", &WsTelescopeInstance::startExposure, this); -} - -WsTelescopeInstance::~WsTelescopeInstance() -{ -} - -void WsTelescopeInstance::startExposure(const json &m_params) -{ - // 实现参数检查 - if (m_params.contains("exposure_time") && m_params["exposure_time"].is_number_integer()) - { - int exposure_time = m_params["exposure_time"]; - if (exposure_time < 1) - { - json error_message = {{"error_code", ServerError::InvalidParameters}, - {"error_message", "exposure_time must be greater than 0"}}; - sendMessage(error_message.dump()); - return; - } - if (exposure_time > 1000000) - { - json error_message = {{"error_code", ServerError::InvalidParameters}, - {"error_message", "exposure_time must be less than 1000000"}}; - sendMessage(error_message.dump()); - return; - } - } - else - { - json error_message = {{"error_code", ServerError::InvalidParameters}, - {"error_message", "exposure_time must be an integer"}}; - sendMessage(error_message.dump()); - return; - } -} \ No newline at end of file diff --git a/src/websocket/device/WsTelescopeInstance.hpp b/src/websocket/device/WsTelescopeInstance.hpp deleted file mode 100644 index 7305db43..00000000 --- a/src/websocket/device/WsTelescopeInstance.hpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * WsTelescopeInstance.hpp - * - * Copyright (C) 2023 Max Qian - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/************************************************* - -Copyright: 2023 Max Qian. All rights reserved - -Author: Max Qian - -E-mail: astro_air@126.com - -Date: 2023-10-20 - -Description: WebSocket Device Instance (each device each instance) - -**************************************************/ - -#ifndef WSTELESCOPEINSTANCE_HPP -#define WSTELESCOPEINSTANCE_HPP - -#include "WsDeviceInstance.hpp" - -/** - * @brief Class representing an instance of a WebSocket Telescope - * - */ -class WsTelescopeInstance : public WsDeviceInstance -{ - -public: - /** - * @brief Construct a new WsTelescopeInstance object. - * - * @param socket Shared pointer to the AsyncWebSocket object. - * @param hub Shared pointer to the WsDeviceHub object. - * @param device_name Name of the device. - * @param userId Id of the user. - */ - WsTelescopeInstance(const std::shared_ptr &socket, - const std::shared_ptr &hub, - const oatpp::String &device_name, - v_int32 userId); - - /** - * @brief Destroy the WsTelescopeInstance object. - * - */ - ~WsTelescopeInstance(); - -public: - - void startExposure(const json &m_params); -}; - -#endif // WSTELESCOPEINSTANCE_HPP diff --git a/src/websocket/plugin/WsPluginHub.cpp b/src/websocket/plugin/WsPluginHub.cpp deleted file mode 100644 index 96ed4668..00000000 --- a/src/websocket/plugin/WsPluginHub.cpp +++ /dev/null @@ -1,32 +0,0 @@ - -#include "WsPluginHub.hpp" - -void WsPluginHub::addplugin(const std::shared_ptr &plugin) -{ - std::lock_guard guard(m_pluginByIdLock); - m_pluginById[plugin->getUserId()] = plugin; -} - -void WsPluginHub::removepluginByUserId(v_int32 userId) -{ - std::lock_guard guard(m_pluginByIdLock); - m_pluginById.erase(userId); -} - -void WsPluginHub::sendMessage(const oatpp::String &message) -{ - std::lock_guard guard(m_pluginByIdLock); - for (auto &pair : m_pluginById) - { - pair.second->sendMessage(message); - } -} - -void WsPluginHub::sendBinaryMessage(const void *binary_message, int size) -{ - std::lock_guard guard(m_pluginByIdLock); - for (auto &pair : m_pluginById) - { - pair.second->sendBinaryMessage(binary_message, size); - } -} \ No newline at end of file diff --git a/src/websocket/plugin/WsPluginHub.hpp b/src/websocket/plugin/WsPluginHub.hpp deleted file mode 100644 index 65d53f33..00000000 --- a/src/websocket/plugin/WsPluginHub.hpp +++ /dev/null @@ -1,52 +0,0 @@ - -#ifndef WsPluginHUB_HPP -#define WsPluginHUB_HPP - -#include "WsPluginInstance.hpp" - -#if ENABLE_FASTHASH -#include "emhash/hash_table8.hpp" -#else -#include -#endif - -class WsPluginHub -{ -private: - oatpp::String m_name; - std::unordered_map> m_pluginById; - std::mutex m_pluginByIdLock; - -public: - WsPluginHub(const oatpp::String &name) - : m_name(name) - { - } - - /** - * Add plugin to the WsPluginHub. - * @param plugin - */ - void addplugin(const std::shared_ptr &plugin); - - /** - * Remove plugin from the WsPluginHub. - * @param userId - */ - void removepluginByUserId(v_int32 userId); - - /** - * Send message to all plugins in the WsPluginHub. - * @param message - */ - void sendMessage(const oatpp::String &message); - - /** - * Send binary message to all plugins in the WsPluginHub. - * @param binary_message - * @param size - */ - void sendBinaryMessage(const void *binary_message, int size); -}; - -#endif // WsPluginHUB_HPP diff --git a/src/websocket/plugin/WsPluginInstance.cpp b/src/websocket/plugin/WsPluginInstance.cpp deleted file mode 100644 index f8547b30..00000000 --- a/src/websocket/plugin/WsPluginInstance.cpp +++ /dev/null @@ -1,106 +0,0 @@ - -#include "WsPluginInstance.hpp" -#include "WsPluginHub.hpp" - -void WsPluginInstance::sendMessage(const oatpp::String &message) -{ - - class SendMessageCoroutine : public oatpp::async::Coroutine - { - private: - oatpp::async::Lock *m_lock; - std::shared_ptr m_websocket; - oatpp::String m_message; - - public: - SendMessageCoroutine(oatpp::async::Lock *lock, - const std::shared_ptr &websocket, - const oatpp::String &message) - : m_lock(lock), m_websocket(websocket), m_message(message) - { - } - - Action act() override - { - return oatpp::async::synchronize(m_lock, m_websocket->sendOneFrameTextAsync(m_message)).next(finish()); - } - }; - - m_asyncExecutor->execute(&m_writeLock, m_socket, message); -} - -void WsPluginInstance::sendBinaryMessage(const void *binary_message, int size) -{ - oatpp::String binary((const char *)binary_message, size); - class SendMessageCoroutine : public oatpp::async::Coroutine - { - private: - oatpp::async::Lock *m_lock; - std::shared_ptr m_websocket; - oatpp::String m_message; - - public: - SendMessageCoroutine(oatpp::async::Lock *lock, - const std::shared_ptr &websocket, - const oatpp::String &message) - : m_lock(lock), m_websocket(websocket), m_message(message) - { - } - - Action act() override - { - return oatpp::async::synchronize(m_lock, m_websocket->sendOneFrameTextAsync(m_message)).next(finish()); - } - }; - - m_asyncExecutor->execute(&m_writeLock, m_socket, binary); -} - -std::shared_ptr WsPluginInstance::getHub() -{ - return m_hub; -} - -oatpp::String WsPluginInstance::getPluginName() -{ - return m_plugin_name; -} - -v_int32 WsPluginInstance::getUserId() -{ - return m_userId; -} - -oatpp::async::CoroutineStarter WsPluginInstance::onPing(const std::shared_ptr &socket, const oatpp::String &message) -{ - return oatpp::async::synchronize(&m_writeLock, socket->sendPongAsync(message)); -} - -oatpp::async::CoroutineStarter WsPluginInstance::onPong(const std::shared_ptr &socket, const oatpp::String &message) -{ - return nullptr; // do nothing -} - -oatpp::async::CoroutineStarter WsPluginInstance::onClose(const std::shared_ptr &socket, v_uint16 code, const oatpp::String &message) -{ - return nullptr; // do nothing -} - -oatpp::async::CoroutineStarter WsPluginInstance::readMessage(const std::shared_ptr &socket, v_uint8 opcode, p_char8 data, oatpp::v_io_size size) -{ - - if (size == 0) - { // message transfer finished - - auto wholeMessage = m_messageBuffer.toString(); - m_messageBuffer.setCurrentPosition(0); - - m_hub->sendMessage(m_plugin_name + ": " + wholeMessage); - } - else if (size > 0) - { // message frame received - m_messageBuffer.writeSimple(data, size); - } - - return nullptr; // do nothing -} \ No newline at end of file diff --git a/src/websocket/plugin/WsPluginInstance.hpp b/src/websocket/plugin/WsPluginInstance.hpp deleted file mode 100644 index 35103536..00000000 --- a/src/websocket/plugin/WsPluginInstance.hpp +++ /dev/null @@ -1,114 +0,0 @@ - -#ifndef WsPluginINSTANCE_HPP -#define WsPluginINSTANCE_HPP - -#include "config.h" - -#if ENABLE_ASYNC -#include "oatpp-websocket/AsyncWebSocket.hpp" -#else -#include "oatpp-websocket/WebSocket.hpp" -#endif - -#if ENABLE_ASYNC -#include "oatpp/core/async/Lock.hpp" -#include "oatpp/core/async/Executor.hpp" -#endif - -#include "oatpp/core/macro/component.hpp" - -#include "atom/server/commander.hpp" - -#include "LithiumApp.hpp" - -#include - -class WsPluginHub; // FWD - -class WsPluginInstance : public oatpp::websocket::AsyncWebSocket::Listener -{ -private: - /** - * Buffer for messages. Needed for multi-frame messages. - */ - oatpp::data::stream::BufferOutputStream m_messageBuffer; - - /** - * Lock for synchronization of writes to the web socket. - */ - oatpp::async::Lock m_writeLock; - - std::unique_ptr> m_CommandDispatcher; - - template - void LiRegisterFunc(const std::string &name, void (T::*memberFunc)(const json &)) - { - m_CommandDispatcher->RegisterMemberHandler(name, this, memberFunc); - } - - bool LiRunFunc(const std::string &name, const nlohmann::json ¶ms) - { - if (m_CommandDispatcher->HasHandler(name)) - { - m_CommandDispatcher->Dispatch(name, params); - return true; - } - return false; - } - -private: - std::shared_ptr m_socket; - std::shared_ptr m_hub; - oatpp::String m_plugin_name; - v_int32 m_userId; - -private: - /** - * Inject async executor object. - */ - OATPP_COMPONENT(std::shared_ptr, m_asyncExecutor); - -public: - WsPluginInstance(const std::shared_ptr &socket, - const std::shared_ptr &hub, - const oatpp::String &plugin_name, - v_int32 userId) - : m_socket(socket), m_hub(hub), m_plugin_name(plugin_name), m_userId(userId) - { - OATPP_LOGD(m_plugin_name.getValue("").c_str(), "%s created", m_plugin_name.getValue("").c_str()); - } - - /** - * Send message to WsPluginInstance (to user). - * @param message - */ - void sendMessage(const oatpp::String &message); - - void sendBinaryMessage(const void *binary_message, int size); - - /** - * Get hub of the WsPluginInstance. - * @return - */ - std::shared_ptr getHub(); - - /** - * Get WsPluginInstance plugin_name. - * @return - */ - oatpp::String getPluginName(); - - /** - * Get WsPluginInstance userId. - * @return - */ - v_int32 getUserId(); - -public: // WebSocket Listener methods - CoroutineStarter onPing(const std::shared_ptr &socket, const oatpp::String &message) override; - CoroutineStarter onPong(const std::shared_ptr &socket, const oatpp::String &message) override; - CoroutineStarter onClose(const std::shared_ptr &socket, v_uint16 code, const oatpp::String &message) override; - CoroutineStarter readMessage(const std::shared_ptr &socket, v_uint8 opcode, p_char8 data, oatpp::v_io_size size) override; -}; - -#endif // WsPluginINSTANCE_HPP diff --git a/src/websocket/plugin/WsPluginServer.cpp b/src/websocket/plugin/WsPluginServer.cpp deleted file mode 100644 index c6c4faac..00000000 --- a/src/websocket/plugin/WsPluginServer.cpp +++ /dev/null @@ -1,46 +0,0 @@ - -#include "WsPluginServer.hpp" - -v_int32 WsPluginServer::obtainNewUserId() -{ - return m_userIdCounter++; -} - -std::shared_ptr WsPluginServer::getOrCreateHub(const oatpp::String &hubName) -{ - std::lock_guard lock(m_hubsMutex); - std::shared_ptr &hub = m_hubs[hubName]; - if (!hub) - { - hub = std::make_shared(hubName); - } - return hub; -} - -void WsPluginServer::onAfterCreate_NonBlocking(const std::shared_ptr &socket, const std::shared_ptr ¶ms) -{ - auto pluginName = params->find("pluginName")->second; - auto pluginHub = params->find("pluginHub")->second; - auto hub = getOrCreateHub(pluginHub); - - auto plugin = std::make_shared(socket, hub, pluginName, obtainNewUserId()); - socket->setListener(plugin); - - hub->addplugin(plugin); - hub->sendMessage(pluginName + " joined " + pluginHub); -} - -void WsPluginServer::onBeforeDestroy_NonBlocking(const std::shared_ptr &socket) -{ - - auto plugin = std::static_pointer_cast(socket->getListener()); - auto plugin_name = plugin->getPluginName(); - auto hub = plugin->getHub(); - - hub->removepluginByUserId(plugin->getUserId()); - - hub->sendMessage(plugin_name + " left the hub"); - - /* Remove circle `std::shared_ptr` dependencies */ - socket->setListener(nullptr); -} \ No newline at end of file diff --git a/src/websocket/plugin/WsPluginServer.hpp b/src/websocket/plugin/WsPluginServer.hpp deleted file mode 100644 index 0a492167..00000000 --- a/src/websocket/plugin/WsPluginServer.hpp +++ /dev/null @@ -1,60 +0,0 @@ - -#ifndef WsPluginSERVER_HPP -#define WsPluginSERVER_HPP - -#include "WsPluginHub.hpp" - -#include "config.h" - -#if ENABLE_ASYNC -#include "oatpp-websocket/AsyncConnectionHandler.hpp" -#else -#include "oatpp-websocket/ConnectionHandler.hpp" -#endif - -#if ENABLE_FASTHASH -#include "emhash/hash_table8.hpp" -#else -#include -#endif -#include - -class WsPluginServer : public oatpp::websocket::AsyncConnectionHandler::SocketInstanceListener -{ -public: - std::atomic m_userIdCounter; - std::unordered_map> m_hubs; - std::mutex m_hubsMutex; - -public: - WsPluginServer() - : m_userIdCounter(0) - { - } - - /** - * Generate id for new user - * @return - */ - v_int32 obtainNewUserId(); - - /** - * Get plugin hub by name or create new one if not exists. - * @param hubName - * @return - */ - std::shared_ptr getOrCreateHub(const oatpp::String &hubName); - -public: - /** - * Called when socket is created - */ - void onAfterCreate_NonBlocking(const std::shared_ptr &socket, const std::shared_ptr ¶ms) override; - - /** - * Called before socket instance is destroyed. - */ - void onBeforeDestroy_NonBlocking(const std::shared_ptr &socket) override; -}; - -#endif // WsPluginSERVER_HPP diff --git a/websrc/rubidium-web/package-lock.json b/websrc/rubidium-web/package-lock.json new file mode 100644 index 00000000..22ea3050 --- /dev/null +++ b/websrc/rubidium-web/package-lock.json @@ -0,0 +1,14195 @@ +{ + "name": "rubidium-web", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@welldone-software/why-did-you-render": "^7.0.1", + "fomantic-ui": "^2.9.3", + "fullscreen": "^1.1.1", + "isomorphic-fetch": "^3.0.0", + "lodash": "^4.17.21", + "react": "^18.2.0", + "react-native": "^0.73.0", + "react-redux": "^9.0.4", + "redux": "^5.0.0" + } + }, + "node_modules/@actions/core": { + "version": "1.10.1", + "resolved": "https://registry.npmmirror.com/@actions/core/-/core-1.10.1.tgz", + "integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==", + "dependencies": { + "@actions/http-client": "^2.0.1", + "uuid": "^8.3.2" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@actions/http-client/-/http-client-2.2.0.tgz", + "integrity": "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.23.6.tgz", + "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.6", + "@babel/parser": "^7.23.6", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmmirror.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "peer": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.6.tgz", + "integrity": "sha512-cBXU1vZni/CpGF29iTu4YRbOZt3Wat6zCoMDxRF1MayiEc4URxOj31tT65HUM0CRpMowA3HCJaAOVOUnMf96cw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.4", + "resolved": "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.23.6.tgz", + "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", + "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-default-from": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.23.3.tgz", + "integrity": "sha512-Q23MpLZfSGZL1kU7fWqV262q65svLSCIP5kZ/JCW/rKTCm/FrLjpvEd2kfUYMVeHh4QhV/xzyoRAHWrAZJrE3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-default-from": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "peer": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-default-from": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.23.3.tgz", + "integrity": "sha512-KeENO5ck1IeZ/l2lFZNy+mpobV3D2Zy5C1YFnWm+YuY5mQiAWc4yAp13dqgguwsBsFVLh4LPCEqCa5qW13N+hw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.23.3.tgz", + "integrity": "sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", + "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", + "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "peer": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz", + "integrity": "sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-flow": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", + "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "peer": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz", + "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", + "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/types": "^7.23.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.6.tgz", + "integrity": "sha512-kF1Zg62aPseQ11orDhFRw+aPG/eynNQtI+TyY+m33qJa2cJ5EEvza2P2BNTIA9E5MyqFABHEyY6CPHwgdy9aNg==", + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", + "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.23.6.tgz", + "integrity": "sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ==", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.4", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.5", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-flow": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/preset-flow/-/preset-flow-7.23.3.tgz", + "integrity": "sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-transform-flow-strip-types": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", + "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-typescript": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register": { + "version": "7.22.15", + "resolved": "https://registry.npmmirror.com/@babel/register/-/register-7.22.15.tgz", + "integrity": "sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg==", + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.5", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + }, + "node_modules/@babel/runtime": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.23.6.tgz", + "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.23.6.tgz", + "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/@babel/types": { + "version": "7.23.6", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/@fastify/busboy/-/busboy-2.1.0.tgz", + "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmmirror.com/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/core": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/@octokit/core/-/core-3.6.0.tgz", + "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmmirror.com/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmmirror.com/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "12.11.0", + "resolved": "https://registry.npmmirror.com/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", + "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "2.21.3", + "resolved": "https://registry.npmmirror.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", + "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", + "dependencies": { + "@octokit/types": "^6.40.0" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.16.2", + "resolved": "https://registry.npmmirror.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", + "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", + "dependencies": { + "@octokit/types": "^6.39.0", + "deprecation": "^2.3.1" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmmirror.com/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/rest": { + "version": "18.12.0", + "resolved": "https://registry.npmmirror.com/@octokit/rest/-/rest-18.12.0.tgz", + "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", + "dependencies": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.8", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" + } + }, + "node_modules/@octokit/types": { + "version": "6.41.0", + "resolved": "https://registry.npmmirror.com/@octokit/types/-/types-6.41.0.tgz", + "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", + "dependencies": { + "@octokit/openapi-types": "^12.11.0" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@react-native-community/cli": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli/-/cli-12.1.1.tgz", + "integrity": "sha512-St/lyxQ//crrigfE2QCqmjDb0IH3S9nmolm0eqmCA1bB8WWUk5dpjTgQk6xxDxz+3YtMghDJkGZPK4AxDXT42g==", + "dependencies": { + "@react-native-community/cli-clean": "12.1.1", + "@react-native-community/cli-config": "12.1.1", + "@react-native-community/cli-debugger-ui": "12.1.1", + "@react-native-community/cli-doctor": "12.1.1", + "@react-native-community/cli-hermes": "12.1.1", + "@react-native-community/cli-plugin-metro": "12.1.1", + "@react-native-community/cli-server-api": "12.1.1", + "@react-native-community/cli-tools": "12.1.1", + "@react-native-community/cli-types": "12.1.1", + "chalk": "^4.1.2", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "bin": { + "react-native": "build/bin.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native-community/cli-clean": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-clean/-/cli-clean-12.1.1.tgz", + "integrity": "sha512-lbEQJ9xO8DmNbES7nFcGIQC0Q15e9q1zwKfkN2ty2eM93ZTFqYzOwsddlNoRN9FO7diakMWoWgielhcfcIeIrQ==", + "dependencies": { + "@react-native-community/cli-tools": "12.1.1", + "chalk": "^4.1.2", + "execa": "^5.0.0" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli-clean/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-config": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-config/-/cli-config-12.1.1.tgz", + "integrity": "sha512-og8/yH7ZNMBcRJOGaHcn9BLt1WJF3XvgBw8iYsByVSEN7yvzAbYZ+CvfN6EdObGOqendbnE4lN9CVyQYM9Ufsw==", + "dependencies": { + "@react-native-community/cli-tools": "12.1.1", + "chalk": "^4.1.2", + "cosmiconfig": "^5.1.0", + "deepmerge": "^4.3.0", + "glob": "^7.1.3", + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@react-native-community/cli-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-debugger-ui": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.1.1.tgz", + "integrity": "sha512-q427jvbJ0WdDuS6HNdc3EbmUu/dX/+FWCcZI60xB7m1i/8p+LzmrsoR2yIJCricsAIV3hhiFOGfquZDgrbF27Q==", + "dependencies": { + "serve-static": "^1.13.1" + } + }, + "node_modules/@react-native-community/cli-doctor": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-doctor/-/cli-doctor-12.1.1.tgz", + "integrity": "sha512-IUZJ/KUCuz+IzL9GdHUlIf6zF93XadxCBDPseUYb0ucIS+rEb3RmYC+IukYhUWwN3y4F/yxipYy3ytKrQ33AxA==", + "dependencies": { + "@react-native-community/cli-config": "12.1.1", + "@react-native-community/cli-platform-android": "12.1.1", + "@react-native-community/cli-platform-ios": "12.1.1", + "@react-native-community/cli-tools": "12.1.1", + "chalk": "^4.1.2", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.10.0", + "execa": "^5.0.0", + "hermes-profile-transformer": "^0.0.6", + "ip": "^1.1.5", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "semver": "^7.5.2", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-hermes": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-hermes/-/cli-hermes-12.1.1.tgz", + "integrity": "sha512-J6yxQoZooFRT8+Dtz8Px/bwasQxnbxZZFAFQzOs3f6CAfXrcr/+JLVFZRWRv9XGfcuLdCHr22JUVPAnyEd48DA==", + "dependencies": { + "@react-native-community/cli-platform-android": "12.1.1", + "@react-native-community/cli-tools": "12.1.1", + "chalk": "^4.1.2", + "hermes-profile-transformer": "^0.0.6", + "ip": "^1.1.5" + } + }, + "node_modules/@react-native-community/cli-hermes/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-hermes/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli-hermes/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-platform-android": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-platform-android/-/cli-platform-android-12.1.1.tgz", + "integrity": "sha512-jnyc9y5cPltBo518pfVZ53dtKGDy02kkCkSIwv4ltaHYse7JyEFxFbzBn9lloWvbZ0iFHvEo1NN78YGPAlXSDw==", + "dependencies": { + "@react-native-community/cli-tools": "12.1.1", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.2.4", + "glob": "^7.1.3", + "logkitty": "^0.7.1" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-platform-ios": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.1.1.tgz", + "integrity": "sha512-RA2lvFrswwQRIhCV3hoIYZmLe9TkRegpAWimdubtMxRHiv7Eh2dC0VWWR5VdWy3ltbJzeiEpxCoH/EcrMfp9tg==", + "dependencies": { + "@react-native-community/cli-tools": "12.1.1", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.0.12", + "glob": "^7.1.3", + "ora": "^5.4.1" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-plugin-metro": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.1.1.tgz", + "integrity": "sha512-HV+lW1mFSu6GL7du+0/tfq8/5jytKp+w3n4+MWzRkx5wXvUq3oJjzwe8y+ZvvCqkRPdsOiwFDgJrtPhvaZp+xA==" + }, + "node_modules/@react-native-community/cli-server-api": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-server-api/-/cli-server-api-12.1.1.tgz", + "integrity": "sha512-dUqqEmtEiCMyqFd6LF1UqH0WwXirK2tpU7YhyFsBbigBj3hPz2NmzghCe7DRIcC9iouU0guBxhgmiLtmUEPduQ==", + "dependencies": { + "@react-native-community/cli-debugger-ui": "12.1.1", + "@react-native-community/cli-tools": "12.1.1", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^7.5.1" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@react-native-community/cli-tools": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-tools/-/cli-tools-12.1.1.tgz", + "integrity": "sha512-c9vjDVojZnivGsLoVoTZsJjHnwBEI785yV8mgyKTVFx1sciK8lCsIj1Lke7jNpz7UAE1jW94nI7de2B1aQ9rbA==", + "dependencies": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "node-fetch": "^2.6.0", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-types": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-types/-/cli-types-12.1.1.tgz", + "integrity": "sha512-B9lFEIc1/H2GjiyRCk6ISJNn06h5j0cWuokNm3FmeyGOoGIfm4XYUbnM6IpGlIDdQpTtUzZfNq8CL4CIJZXF0g==", + "dependencies": { + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/@react-native-community/cli/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/assets-registry": { + "version": "0.73.1", + "resolved": "https://registry.npmmirror.com/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", + "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.74.0", + "resolved": "https://registry.npmmirror.com/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.0.tgz", + "integrity": "sha512-xAM/eVSb5LBkKue3bDZgt76bdsGGzKeF/iEzUNbDTwRQrB3Q5GoceGNM/zVlF+z1xGAkr3jhL+ZyITZGSoIlgw==", + "dependencies": { + "@react-native/codegen": "*" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.74.0", + "resolved": "https://registry.npmmirror.com/@react-native/babel-preset/-/babel-preset-0.74.0.tgz", + "integrity": "sha512-k+1aaYQeLn+GBmGA5Qs3NKI8uzhLvRRMML+pB/+43ZL6DvCklbuJ5KO5oqRRpF3KZ2t/VKUqqSichpXfFrXGjg==", + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.18.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.20.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "@react-native/babel-plugin-codegen": "*", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.73.2", + "resolved": "https://registry.npmmirror.com/@react-native/codegen/-/codegen-0.73.2.tgz", + "integrity": "sha512-lfy8S7umhE3QLQG5ViC4wg5N1Z+E6RnaeIw8w1voroQsXXGPB72IBozh8dAHR3+ceTxIU0KX3A8OpJI8e1+HpQ==", + "dependencies": { + "@babel/parser": "^7.20.0", + "flow-parser": "^0.206.0", + "glob": "^7.1.1", + "invariant": "^2.2.4", + "jscodeshift": "^0.14.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/@react-native/codegen/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@react-native/codegen/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.73.11", + "resolved": "https://registry.npmmirror.com/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.11.tgz", + "integrity": "sha512-s0bprwljKS1Al8wOKathDDmRyF+70CcNE2G/aqZ7+L0NoOE0Uxxx/5P2BxlM2Mfht7O33B4SeMNiPdE/FqIubQ==", + "dependencies": { + "@react-native-community/cli-server-api": "12.3.0", + "@react-native-community/cli-tools": "12.3.0", + "@react-native/dev-middleware": "^0.73.6", + "@react-native/metro-babel-transformer": "^0.73.12", + "chalk": "^4.0.0", + "execa": "^5.1.1", + "metro": "^0.80.0", + "metro-config": "^0.80.0", + "metro-core": "^0.80.0", + "node-fetch": "^2.2.0", + "readline": "^1.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-debugger-ui": { + "version": "12.3.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.0.tgz", + "integrity": "sha512-w3b0iwjQlk47GhZWHaeTG8kKH09NCMUJO729xSdMBXE8rlbm4kHpKbxQY9qKb6NlfWSJN4noGY+FkNZS2rRwnQ==", + "dependencies": { + "serve-static": "^1.13.1" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-server-api": { + "version": "12.3.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-server-api/-/cli-server-api-12.3.0.tgz", + "integrity": "sha512-Rode8NrdyByC+lBKHHn+/W8Zu0c+DajJvLmOWbe2WY/ECvnwcd9MHHbu92hlT2EQaJ9LbLhGrSbQE3cQy9EOCw==", + "dependencies": { + "@react-native-community/cli-debugger-ui": "12.3.0", + "@react-native-community/cli-tools": "12.3.0", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^7.5.1" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-tools": { + "version": "12.3.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-tools/-/cli-tools-12.3.0.tgz", + "integrity": "sha512-2GafnCr8D88VdClwnm9KZfkEb+lzVoFdr/7ybqhdeYM0Vnt/tr2N+fM1EQzwI1DpzXiBzTYemw8GjRq+Utcz2Q==", + "dependencies": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "node-fetch": "^2.6.0", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.73.3", + "resolved": "https://registry.npmmirror.com/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", + "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.73.6", + "resolved": "https://registry.npmmirror.com/@react-native/dev-middleware/-/dev-middleware-0.73.6.tgz", + "integrity": "sha512-9SD7gIso+hO1Jy1Y/Glbd+JWQwyH7Xjnwebtkxdm5TMB51LQPjaGtMcwEigbIZyAtvoaDGmhWmudwbKpDlS+gA==", + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "^0.73.3", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^1.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "node-fetch": "^2.2.0", + "open": "^7.0.3", + "serve-static": "^1.13.1", + "temp-dir": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmmirror.com/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.73.4", + "resolved": "https://registry.npmmirror.com/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz", + "integrity": "sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/js-polyfills": { + "version": "0.73.1", + "resolved": "https://registry.npmmirror.com/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", + "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.73.12", + "resolved": "https://registry.npmmirror.com/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.12.tgz", + "integrity": "sha512-VmxN5aaoOprzDzUR+8c3XYhG0FoMOO6n0ToylCW6EeZCuf5RTY7HWVOhacabGoB1mHrWzJ0wWEsqX+eD4iFxoA==", + "dependencies": { + "@babel/core": "^7.20.0", + "@react-native/babel-preset": "*", + "babel-preset-fbjs": "^3.4.0", + "hermes-parser": "0.15.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.73.2", + "resolved": "https://registry.npmmirror.com/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", + "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==" + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.73.4", + "resolved": "https://registry.npmmirror.com/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", + "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmmirror.com/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmmirror.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmmirror.com/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "20.10.4", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.10.4.tgz", + "integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, + "node_modules/@types/vinyl": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/vinyl/-/vinyl-2.0.11.tgz", + "integrity": "sha512-vPXzCLmRp74e9LsP8oltnWKTH+jBwt86WgRUb4Pc9Lf3pkMVGyvIo2gm9bODeGfCay2DBB/hAWDuvf07JcK4rw==", + "dependencies": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmmirror.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" + }, + "node_modules/@welldone-software/why-did-you-render": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/@welldone-software/why-did-you-render/-/why-did-you-render-7.0.1.tgz", + "integrity": "sha512-Qe/8Xxa2G+LMdI6VoazescPzjjkHYduCDa8aHOJR50e9Bgs8ihkfMBY+ev7B4oc3N59Zm547Sgjf8h5y0FOyoA==", + "dependencies": { + "lodash": "^4" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18" + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/add-event-listener": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/add-event-listener/-/add-event-listener-0.0.1.tgz", + "integrity": "sha512-hjRmkeDqFUWEFcDHP/Lp0Pa4MhIJk/oQX8B7lFiNrjBKHjf0q+ivCJrucY8d8UI5d0QkZgV2jGdAGXxEZcm3nA==" + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmmirror.com/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==" + }, + "node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha512-eCjan3AVo/SxZ0/MyIYRtkpxIu/H3xZN7URr1vXVrISxeyz8fUFz0FJziamK4sS8I+t35y4rHg1b2PklyBe/7A==", + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "dependencies": { + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "node_modules/ansi-fragments/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-fragments/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==", + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/any-shell-escape": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/any-shell-escape/-/any-shell-escape-0.1.1.tgz", + "integrity": "sha512-36j4l5HVkboyRhIWgtMh1I9i8LTdFqVwDEHy1cp+QioJyKgAUG40X0W8s7jakWRta/Sjvm8mUG1fU6Tj8mWagQ==" + }, + "node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/anymatch/node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/anymatch/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/appdirsjs": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/appdirsjs/-/appdirsjs-1.2.7.tgz", + "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==" + }, + "node_modules/append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", + "dependencies": { + "buffer-equal": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha512-A2BETWCqhsecSvCkWAeVBFLH6sXEUGASuzkpjL3GR1SlL/PWL6M3J8EAAld2Uubmh39tvkJTqC9LeLHCUKmFXA==", + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==", + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha512-BC4Yl89vneCYfpLrs5JU2aAu9/a+xWbeKhvISg9PT7eWFB9UlRvI+rKEtk6mgxWr3dSkk9gQ8hCrdqt06NXPdw==", + "dependencies": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-initial/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dependencies": { + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-last/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dependencies": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ast-types": { + "version": "0.15.2", + "resolved": "https://registry.npmmirror.com/ast-types/-/ast-types-0.15.2.tgz", + "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmmirror.com/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==" + }, + "node_modules/async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/async-each": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/async-each/-/async-each-1.0.6.tgz", + "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==" + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "node_modules/async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==", + "dependencies": { + "async-done": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.16", + "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.16.tgz", + "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "dependencies": { + "browserslist": "^4.21.10", + "caniuse-lite": "^1.0.30001538", + "fraction.js": "^4.3.6", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmmirror.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.7", + "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", + "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.4", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.7", + "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", + "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.4", + "core-js-compat": "^3.33.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", + "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.4" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-syntax-trailing-function-commas": { + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmmirror.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", + "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==" + }, + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "dependencies": { + "@babel/plugin-syntax-flow": "^7.12.1" + } + }, + "node_modules/babel-preset-fbjs": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", + "integrity": "sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==", + "dependencies": { + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-syntax-class-properties": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-block-scoped-functions": "^7.0.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.0.0", + "@babel/plugin-transform-flow-strip-types": "^7.0.0", + "@babel/plugin-transform-for-of": "^7.0.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-member-expression-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-property-literals": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-template-literals": "^7.0.0", + "babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/bach": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/bach/-/bach-1.2.0.tgz", + "integrity": "sha512-bZOOfCb3gXBXbTFXq3OZtGR88LwGeJvzu6szttaIzymOTS4ZttBNOWSv7aLZja2EMycKtRYV0Oa8SNKH/zkxvg==", + "dependencies": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmmirror.com/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "node_modules/beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha512-3vqtKL1N45I5dV0RdssXZG7X6pCqQrWPNOlBPZPrd+QkE2HEhR57Z04m0KtpbsZH73j+a3F8UD1TQnn+ExTvIA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, + "node_modules/better-console": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/better-console/-/better-console-1.0.1.tgz", + "integrity": "sha512-M/azU25cj3ZHbMSoXEroDfzcolfUvM03PZw5EEBk9T3tqdIYfMXrIkEKb9q8OZMC8Hic8Q9l8jk6TZq9cyRrcw==", + "dependencies": { + "chalk": "^1.1.3", + "cli-table": "~0.3.1" + } + }, + "node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/binaryextensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binaryextensions/-/binaryextensions-2.3.0.tgz", + "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "dependencies": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/buffer-equal/-/buffer-equal-1.0.1.tgz", + "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + } + }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001570", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz", + "integrity": "sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==" + }, + "node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/chokidar/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmmirror.com/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chrome-launcher/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-edge-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", + "integrity": "sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "node_modules/chromium-edge-launcher/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmmirror.com/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-css": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/clean-css/-/clean-css-4.2.3.tgz", + "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmmirror.com/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone-deep/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==" + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha512-5D2XXSpkOnleOI21TG7p3T0bGAsZ/XknZpKBmGYyluO8pw4zA3K8ZlrBIbC4FXg3m6z/RNFiUFfT2sQK01+UHA==", + "dependencies": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" + }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmmirror.com/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmmirror.com/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmmirror.com/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmmirror.com/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/convert-source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-0.3.5.tgz", + "integrity": "sha512-+4nRk0k3oEpwUB7/CalD7xE2z4VmtEnnq0GO2IPTkrooTrAhEsWvuLF5iWP1dXrwluki/azwXV1ve7gtYuPldg==" + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dependencies": { + "is-what": "^3.14.1" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-props": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/copy-props/-/copy-props-2.0.5.tgz", + "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", + "dependencies": { + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.34.0", + "resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.34.0.tgz", + "integrity": "sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==", + "dependencies": { + "browserslist": "^4.22.2" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cosmiconfig/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css": { + "version": "2.2.4", + "resolved": "https://registry.npmmirror.com/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dependencies": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==", + "engines": { + "node": "*" + } + }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deep-assign": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/deep-assign/-/deep-assign-1.0.0.tgz", + "integrity": "sha512-iAL1PDjxqhANx86VhUjK0HSb4bozMfJUK64rxdrlWPCgMv7rBvP6AFySY69e+k8JAtPHNWoTsQT5OJvE+Jgpjg==", + "dependencies": { + "is-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dependencies": { + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha512-2xaP6GiwVwOEbXCGoJ4ufgC76m8cj805jrghScewJC2ZDsb9U0b4BIrba+xt/Uytyd0HvQ6+WymSRTfnYj59GQ==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/defaults/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del": { + "version": "6.1.1", + "resolved": "https://registry.npmmirror.com/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "dependencies": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/deprecated-react-native-prop-types": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", + "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", + "dependencies": { + "@react-native/normalize-colors": "^0.73.0", + "invariant": "^2.2.4", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/diff/-/diff-1.0.8.tgz", + "integrity": "sha512-1zEb73vemXFpUmfh3fsta4YHz3lwebxXvaWmPbFv9apujQBWDnkrPDLXLQs1gZo4RCWMDsT89r0Pf/z8/02TGA==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha512-+AWBwjGadtksxjOQSFDhPNQbed7icNXApT4+2BNpsXzcCBiInq2H9XW0O8sfHFaPmnQRs7cg/P0fAr2IWQSW0g==", + "dependencies": { + "readable-stream": "~1.1.9" + } + }, + "node_modules/duplexer2/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmmirror.com/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dependencies": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "node_modules/each-props/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.613", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.613.tgz", + "integrity": "sha512-r4x5+FowKG6q+/Wj0W9nidx7QO31BJwmR2uEo+Qh3YLGQ8SbBAFuDFpTxzly/I2gsbrFwBuIjrMp423L3O5U3w==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/envinfo": { + "version": "7.11.0", + "resolved": "https://registry.npmmirror.com/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "dependencies": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmmirror.com/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dependencies": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==" + }, + "node_modules/fast-xml-parser": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz", + "integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, + "node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/findup-sync/node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/findup-sync/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fined": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fined/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/first-chunk-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", + "integrity": "sha512-X8Z+b/0L4lToKYq+lwnKqi9X/Zek0NibLpsJgVsSxpoYq7JtiCtRb5HqKVEjEw/qAb/4AKKRLOwwKHlWNpm2Eg==", + "dependencies": { + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==" + }, + "node_modules/flow-parser": { + "version": "0.206.0", + "resolved": "https://registry.npmmirror.com/flow-parser/-/flow-parser-0.206.0.tgz", + "integrity": "sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/fomantic-ui": { + "version": "2.9.3", + "resolved": "https://registry.npmmirror.com/fomantic-ui/-/fomantic-ui-2.9.3.tgz", + "integrity": "sha512-M8RcLEdEzNCAQCoPTr8ZUk8g19FZsOTP1jrb1rwCyF4cx2KwtXUUkRHCChQHRbDcLZg4IlXClYdusXfjZHjTdA==", + "dependencies": { + "@actions/core": "^1.6.0", + "@octokit/core": "^3.6.0", + "@octokit/rest": "^18.12.0", + "better-console": "^1.0.1", + "browserslist": "^4.21.4", + "del": "^6.1.1", + "extend": "^3.0.2", + "gulp": "^4.0.0", + "gulp-autoprefixer": "^8.0.0", + "gulp-chmod": "^2.0.0", + "gulp-clean-css": "^4.3.0", + "gulp-clone": "^2.0.1", + "gulp-concat": "^2.6.1", + "gulp-concat-css": "^3.1.0", + "gulp-concat-filenames": "^1.2.0", + "gulp-copy": "^4.0.0", + "gulp-dedupe": "^0.0.2", + "gulp-flatten": "^0.4.0", + "gulp-git": "^2.9.0", + "gulp-header": "^2.0.5", + "gulp-if": "^2.0.2", + "gulp-json-editor": "^2.4.3", + "gulp-less": "^5.0.0", + "gulp-plumber": "^1.1.0", + "gulp-print": "^5.0.0", + "gulp-rename": "^1.4.0", + "gulp-replace": "^1.0.0", + "gulp-rtlcss": "^2.0.0", + "gulp-tap": "^1.0.1", + "gulp-uglify": "^3.0.1", + "inquirer": "^8.2.0", + "jquery": "^3.4.0", + "less": "^3.12.0 || ^4.0.0", + "map-stream": "^0.1.0", + "merge-stream": "^2.0.0", + "mkdirp": "^1.0.4", + "normalize-path": "^3.0.0", + "replace-ext": "^1.0.0", + "require-dot-file": "^0.4.0", + "wrench-sui": "^0.0.3", + "yamljs": "^0.3.0" + }, + "engines": { + "node": ">=12", + "npm": ">=6.14.8" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/fork-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmmirror.com/fork-stream/-/fork-stream-0.0.4.tgz", + "integrity": "sha512-Pqq5NnT78ehvUnAk/We/Jr22vSvanRlFTpAmQ88xBY/M1TlHe+P0ILuEyXS595ysdGfaj22634LBkGMA2GTcpA==" + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "engines": { + "node": "*" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==", + "dependencies": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fs-mkdirp-stream/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/fullscreen": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/fullscreen/-/fullscreen-1.1.1.tgz", + "integrity": "sha512-l9h4AZsE1KSwyDRcuwxGe1L1HaVNsFDhJc0BYNdEwxoJW0N/osjvk3CXz2jCZNXO5EPHCWU28LsrC8kg1t1IXA==", + "dependencies": { + "add-event-listener": "0.0.1" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, + "node_modules/get-imports": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/get-imports/-/get-imports-1.0.0.tgz", + "integrity": "sha512-9FjKG2Os+o/EuOIh3B/LNMbU2FWPGHVy/gs9TJpytK95IPl7lLqiu+VAU7JX6VZimqdmpLemgsGMdQWdKvqYGQ==", + "dependencies": { + "array-uniq": "^1.0.1", + "import-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmmirror.com/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", + "dependencies": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/glob-stream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-stream/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-stream/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher": { + "version": "5.0.5", + "resolved": "https://registry.npmmirror.com/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "dependencies": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "dependencies": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-autoprefixer": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/gulp-autoprefixer/-/gulp-autoprefixer-8.0.0.tgz", + "integrity": "sha512-sVR++PIaXpa81p52dmmA/jt50bw0egmylK5mjagfgOJ8uLDGaF9tHyzvetkY9Uo0gBZUS5sVqN3kX/GlUKOyog==", + "dependencies": { + "autoprefixer": "^10.2.6", + "fancy-log": "^1.3.3", + "plugin-error": "^1.0.1", + "postcss": "^8.3.0", + "through2": "^4.0.2", + "vinyl-sourcemaps-apply": "^0.2.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "gulp": ">=4" + }, + "peerDependenciesMeta": { + "gulp": { + "optional": true + } + } + }, + "node_modules/gulp-chmod": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/gulp-chmod/-/gulp-chmod-2.0.0.tgz", + "integrity": "sha512-ttOK11mugzcy6D5CQD8rXqS7M4Ecoo64bDNhRXT9Yok9ztAcOeIK8hsv7LlV1eFS4iSQKZETvEZC5Kt/sH74sw==", + "dependencies": { + "deep-assign": "^1.0.0", + "stat-mode": "^0.2.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-chmod/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-clean-css": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/gulp-clean-css/-/gulp-clean-css-4.3.0.tgz", + "integrity": "sha512-mGyeT3qqFXTy61j0zOIciS4MkYziF2U594t2Vs9rUnpkEHqfu6aDITMp8xOvZcvdX61Uz3y1mVERRYmjzQF5fg==", + "dependencies": { + "clean-css": "4.2.3", + "plugin-error": "1.0.1", + "through2": "3.0.1", + "vinyl-sourcemaps-apply": "0.2.1" + } + }, + "node_modules/gulp-clean-css/node_modules/through2": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dependencies": { + "readable-stream": "2 || 3" + } + }, + "node_modules/gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "dependencies": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-clone": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/gulp-clone/-/gulp-clone-2.0.1.tgz", + "integrity": "sha512-SLg/KsHBbinR/pCX3PF5l1YlR28hLp0X+bcpf77PtMJ6zvAQ5kRjtCPV5Wt1wHXsXWZN0eTUZ15R8ZYpi/CdCA==", + "dependencies": { + "plugin-error": "^0.1.2", + "through2": "^2.0.3" + } + }, + "node_modules/gulp-clone/node_modules/arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", + "dependencies": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clone/node_modules/arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clone/node_modules/array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmmirror.com/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clone/node_modules/extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", + "dependencies": { + "kind-of": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clone/node_modules/kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clone/node_modules/plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", + "dependencies": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clone/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-concat": { + "version": "2.6.1", + "resolved": "https://registry.npmmirror.com/gulp-concat/-/gulp-concat-2.6.1.tgz", + "integrity": "sha512-a2scActrQrDBpBbR3WUZGyGS1JEPLg5PZJdIa7/Bi3GuKAmPYDK6SFhy/NZq5R8KsKKFvtfR0fakbUCcKGCCjg==", + "dependencies": { + "concat-with-sourcemaps": "^1.0.0", + "through2": "^2.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-concat-css": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/gulp-concat-css/-/gulp-concat-css-3.1.0.tgz", + "integrity": "sha512-iLTBPS+cutlgLyK3bp9DMts+WuS8n2mQpjzQ7p/ZVQc8FO5fvpN+ntg9U6jsuNvPeuii82aKm8XeOzF0nUK+TA==", + "dependencies": { + "lodash.defaults": "^3.0.0", + "parse-import": "^2.0.0", + "plugin-error": "^0.1.2", + "rework": "~1.0.0", + "rework-import": "^2.0.0", + "rework-plugin-url": "^1.0.1", + "through2": "~1.1.1", + "vinyl": "^2.1.0" + } + }, + "node_modules/gulp-concat-css/node_modules/arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", + "dependencies": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-concat-css/node_modules/arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-concat-css/node_modules/array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmmirror.com/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-concat-css/node_modules/extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", + "dependencies": { + "kind-of": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-concat-css/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/gulp-concat-css/node_modules/kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-concat-css/node_modules/plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", + "dependencies": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-concat-css/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/gulp-concat-css/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/gulp-concat-css/node_modules/through2": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/through2/-/through2-1.1.1.tgz", + "integrity": "sha512-zEbpaeSMHxczpTzO1KkMHjBC1enTA68ojeaZGG4toqdASpb9t4xUZaYFBq2/9OHo5nTGFVSYd4c910OR+6wxbQ==", + "dependencies": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/gulp-concat-filenames": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gulp-concat-filenames/-/gulp-concat-filenames-1.2.0.tgz", + "integrity": "sha512-2wHcntxftYa2kiv5QOaniSNQuRf1axHGqkyXhRoCBXAVvwzrUp++qW9GNSAdvb3h+7m8yC8Fu25guuaDU+1WaA==", + "dependencies": { + "gulp-util": "3.x.x", + "through": "2.x.x" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-concat/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-copy": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/gulp-copy/-/gulp-copy-4.0.1.tgz", + "integrity": "sha512-UbdAwmEiVNNv55KAiUYWOP6Za7h8JPHNNyekNx8Gyc5XRlpUzTrlEclps939nOeiDPsd6jUtT2LmfavJirbZQg==", + "dependencies": { + "gulp": "^4.0.0", + "plugin-error": "^0.1.2", + "through2": "^2.0.3" + } + }, + "node_modules/gulp-copy/node_modules/arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", + "dependencies": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-copy/node_modules/arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-copy/node_modules/array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmmirror.com/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-copy/node_modules/extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", + "dependencies": { + "kind-of": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-copy/node_modules/kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-copy/node_modules/plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", + "dependencies": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-copy/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-dedupe": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/gulp-dedupe/-/gulp-dedupe-0.0.2.tgz", + "integrity": "sha512-Y+FZmAVHUYDgJiGneLXY2sCErvcY89sskjGQILhh5YvNGZq5M+pKsY54K0MyquZGxj2g10ZDVM5vQnEP7yUrVA==", + "dependencies": { + "colors": "~1.0.2", + "diff": "~1.0.8", + "gulp-util": "~3.0.1", + "lodash.defaults": "~2.4.1", + "through": "~2.3.6" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/gulp-dedupe/node_modules/lodash.defaults": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/lodash.defaults/-/lodash.defaults-2.4.1.tgz", + "integrity": "sha512-5wTIPWwGGr07JFysAZB8+7JB2NjJKXDIwogSaRX5zED85zyUAQwtOqUk8AsJkkigUcL3akbHYXd5+BPtTGQPZw==", + "dependencies": { + "lodash._objecttypes": "~2.4.1", + "lodash.keys": "~2.4.1" + } + }, + "node_modules/gulp-dedupe/node_modules/lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha512-ZpJhwvUXHSNL5wYd1RM6CUa2ZuqorG9ngoJ9Ix5Cce+uX7I5O/E06FCJdhSZ33b5dVyeQDnIlWH7B2s5uByZ7g==", + "dependencies": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "node_modules/gulp-flatten": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/gulp-flatten/-/gulp-flatten-0.4.0.tgz", + "integrity": "sha512-eg4spVTAiv1xXmugyaCxWne1oPtNG0UHEtABx5W8ScLiqAYceyYm6GYA36x0Qh8KOIXmAZV97L2aYGnKREG3Sg==", + "dependencies": { + "plugin-error": "^0.1.2", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/gulp-flatten/node_modules/arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", + "dependencies": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-flatten/node_modules/arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-flatten/node_modules/array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmmirror.com/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-flatten/node_modules/extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", + "dependencies": { + "kind-of": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-flatten/node_modules/kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-flatten/node_modules/plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", + "dependencies": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-flatten/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-git": { + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/gulp-git/-/gulp-git-2.10.1.tgz", + "integrity": "sha512-qiXYYDXchMZU/AWAgtphi4zbJb/0gXgfPw7TlZwu/7qPS3Bdcc3zbVe1B0xY9S8on6RQTmWoi+KaTGACIXQeNg==", + "dependencies": { + "any-shell-escape": "^0.1.1", + "fancy-log": "^1.3.2", + "lodash.template": "^4.4.0", + "plugin-error": "^1.0.1", + "require-dir": "^1.0.0", + "strip-bom-stream": "^3.0.0", + "through2": "^2.0.3", + "vinyl": "^2.0.1" + }, + "engines": { + "node": ">= 0.9.0" + } + }, + "node_modules/gulp-git/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-header": { + "version": "2.0.9", + "resolved": "https://registry.npmmirror.com/gulp-header/-/gulp-header-2.0.9.tgz", + "integrity": "sha512-LMGiBx+qH8giwrOuuZXSGvswcIUh0OiioNkUpLhNyvaC6/Ga8X6cfAeme2L5PqsbXMhL8o8b/OmVqIQdxprhcQ==", + "dependencies": { + "concat-with-sourcemaps": "^1.1.0", + "lodash.template": "^4.5.0", + "map-stream": "0.0.7", + "through2": "^2.0.0" + } + }, + "node_modules/gulp-header/node_modules/map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmmirror.com/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==" + }, + "node_modules/gulp-header/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-if": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/gulp-if/-/gulp-if-2.0.2.tgz", + "integrity": "sha512-tV0UfXkZodpFq6CYxEqH8tqLQgN6yR9qOhpEEN3O6N5Hfqk3fFLcbAavSex5EqnmoQjyaZ/zvgwclvlTI1KGfw==", + "dependencies": { + "gulp-match": "^1.0.3", + "ternary-stream": "^2.0.1", + "through2": "^2.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/gulp-if/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-json-editor": { + "version": "2.5.7", + "resolved": "https://registry.npmmirror.com/gulp-json-editor/-/gulp-json-editor-2.5.7.tgz", + "integrity": "sha512-0Neuyv9tSt2QjjgEsQHmRnIKe+ofqhWShOYiSGHJWgzx49O3s2zwrsK3lcsNVqWIgtonKbVVOKUT0dYvJbQTUg==", + "dependencies": { + "deepmerge": "^4.3.1", + "detect-indent": "^6.1.0", + "js-beautify": "^1.14.8", + "plugin-error": "^2.0.1", + "through2": "^4.0.2" + } + }, + "node_modules/gulp-json-editor/node_modules/plugin-error": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/plugin-error/-/plugin-error-2.0.1.tgz", + "integrity": "sha512-zMakqvIDyY40xHOvzXka0kUvf40nYIuwRE8dWhti2WtjQZ31xAgBZBhxsK7vK3QbRXS1Xms/LO7B5cuAsfB2Gg==", + "dependencies": { + "ansi-colors": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-less": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/gulp-less/-/gulp-less-5.0.0.tgz", + "integrity": "sha512-W2I3TewO/By6UZsM/wJG3pyK5M6J0NYmJAAhwYXQHR+38S0iDtZasmUgFCH3CQj+pQYw/PAIzxvFvwtEXz1HhQ==", + "dependencies": { + "less": "^3.7.1 || ^4.0.0", + "object-assign": "^4.0.1", + "plugin-error": "^1.0.0", + "replace-ext": "^2.0.0", + "through2": "^4.0.0", + "vinyl-sourcemaps-apply": "^0.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-less/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/gulp-match": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/gulp-match/-/gulp-match-1.1.0.tgz", + "integrity": "sha512-DlyVxa1Gj24DitY2OjEsS+X6tDpretuxD6wTfhXE/Rw2hweqc1f6D/XtsJmoiCwLWfXgR87W9ozEityPCVzGtQ==", + "dependencies": { + "minimatch": "^3.0.3" + } + }, + "node_modules/gulp-plumber": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/gulp-plumber/-/gulp-plumber-1.2.1.tgz", + "integrity": "sha512-mctAi9msEAG7XzW5ytDVZ9PxWMzzi1pS2rBH7lA095DhMa6KEXjm+St0GOCc567pJKJ/oCvosVAZEpAey0q2eQ==", + "dependencies": { + "chalk": "^1.1.3", + "fancy-log": "^1.3.2", + "plugin-error": "^0.1.2", + "through2": "^2.0.3" + }, + "engines": { + "node": ">=0.10", + "npm": ">=1.2.10" + } + }, + "node_modules/gulp-plumber/node_modules/arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", + "dependencies": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmmirror.com/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", + "dependencies": { + "kind-of": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", + "dependencies": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-print": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/gulp-print/-/gulp-print-5.0.2.tgz", + "integrity": "sha512-iIpHMzC/b3gFvVXOfP9Jk94SWGIsDLVNUrxULRleQev+08ug07mh84b1AOlW6QDQdmInQiqDFqJN1UvhU2nXdg==", + "dependencies": { + "ansi-colors": "^3.2.4", + "fancy-log": "^1.3.3", + "map-stream": "0.0.7", + "vinyl": "^2.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-print/node_modules/ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-print/node_modules/map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmmirror.com/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==" + }, + "node_modules/gulp-rename": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/gulp-rename/-/gulp-rename-1.4.0.tgz", + "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-replace": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/gulp-replace/-/gulp-replace-1.1.4.tgz", + "integrity": "sha512-SVSF7ikuWKhpAW4l4wapAqPPSToJoiNKsbDoUnRrSgwZHH7lH8pbPeQj1aOVYQrbZKhfSVBxVW+Py7vtulRktw==", + "dependencies": { + "@types/node": "*", + "@types/vinyl": "^2.0.4", + "istextorbinary": "^3.0.0", + "replacestream": "^4.0.3", + "yargs-parser": ">=5.0.0-security.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gulp-rtlcss": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/gulp-rtlcss/-/gulp-rtlcss-2.0.0.tgz", + "integrity": "sha512-muhh8WGEt6P3qyQ9bfCpmALuNod0W4kfwDqrMOQswx0HlyQym1sZlrCUi/pG58LwUpchVJdNS0J+b8hvuTFFbg==", + "dependencies": { + "plugin-error": "^1.0.1", + "rtlcss": "^3.5.0", + "through2": "^2.0.5", + "vinyl-sourcemaps-apply": "^0.2.1" + } + }, + "node_modules/gulp-rtlcss/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-tap": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/gulp-tap/-/gulp-tap-1.0.1.tgz", + "integrity": "sha512-VpCARRSyr+WP16JGnoIg98/AcmyQjOwCpQgYoE35CWTdEMSbpgtAIK2fndqv2yY7aXstW27v3ZNBs0Ltb0Zkbg==", + "dependencies": { + "through2": "^2.0.3" + } + }, + "node_modules/gulp-tap/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-uglify": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/gulp-uglify/-/gulp-uglify-3.0.2.tgz", + "integrity": "sha512-gk1dhB74AkV2kzqPMQBLA3jPoIAPd/nlNzP2XMDSG8XZrqnlCiDGAqC+rZOumzFvB5zOphlFh6yr3lgcAb/OOg==", + "dependencies": { + "array-each": "^1.0.1", + "extend-shallow": "^3.0.2", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "isobject": "^3.0.1", + "make-error-cause": "^1.1.1", + "safe-buffer": "^5.1.2", + "through2": "^2.0.0", + "uglify-js": "^3.0.5", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "node_modules/gulp-uglify/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-uglify/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-uglify/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-uglify/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmmirror.com/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha512-q5oWPc12lwSFS9h/4VIjG+1NuNDlJ48ywV2JKItY4Ycc/n1fXJeYPVQsfu5ZrhQi7FGSDBalwUCLar/GyHXKGw==", + "deprecated": "gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5", + "dependencies": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/gulp-util/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/gulp-util/node_modules/clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha512-dhUqc57gSMCo6TX85FLfe51eC/s+Im2MLkAgJwfaRRexR2tA4dd3eLEW4L6efzHc2iNorrRRXITifnDLlRrhaA==" + }, + "node_modules/gulp-util/node_modules/lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha512-0B4Y53I0OgHUJkt+7RmlDFWKjVAI/YUpWNiL9GQz5ORDr4ttgfQGo+phBWKFLJbBdtOwgMuUkdOHOnPg45jKmQ==", + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "node_modules/gulp-util/node_modules/lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha512-TcrlEr31tDYnWkHFWDCV3dHYroKEXpJZ2YJYvJdhN+y4AkWMDZ5I4I8XDtUKqSAyG81N7w+I1mFEJtcED+tGqQ==", + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, + "node_modules/gulp-util/node_modules/object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha512-AFBWBy9EVRTa/LhEcG8QDP3FvpwZqmvN2QFDuJswFeaVhWnZMp8q3E6Zd90SR04PlIwfGdyVjNyLPyen/ek5CQ==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gulp-util/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-util/node_modules/vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmmirror.com/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha512-P5zdf3WB9uzr7IFoVQ2wZTmUwHL8cMZWJGzLBNCHNZ3NB6HTMsYABtt7z8tAGIINLXyAob9B9a1yzVGMFOYKEA==", + "dependencies": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + }, + "engines": { + "node": ">= 0.9" + } + }, + "node_modules/gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==", + "dependencies": { + "glogg": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha512-+F4GzLjwHNNDEAJW2DC1xXfEoPkRDmUdJ7CBYw4MpqtDwOnqdImJl7GWlpqx+Wko6//J8uKTnIe4wZSv7yCqmw==", + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dependencies": { + "get-intrinsic": "^1.2.2" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.15.0", + "resolved": "https://registry.npmmirror.com/hermes-estree/-/hermes-estree-0.15.0.tgz", + "integrity": "sha512-lLYvAd+6BnOqWdnNbP/Q8xfl8LOGw4wVjfrNd9Gt8eoFzhNBRVD95n4l2ksfMVOoxuVyegs85g83KS9QOsxbVQ==" + }, + "node_modules/hermes-parser": { + "version": "0.15.0", + "resolved": "https://registry.npmmirror.com/hermes-parser/-/hermes-parser-0.15.0.tgz", + "integrity": "sha512-Q1uks5rjZlE9RjMMjSUCkGrEIPI5pKJILeCtK1VmTj7U4pf3wVPoo+cxfu+s4cBAPy2JzikIIdCZgBoR6x7U1Q==", + "dependencies": { + "hermes-estree": "0.15.0" + } + }, + "node_modules/hermes-profile-transformer": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", + "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", + "dependencies": { + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-profile-transformer/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/import-regex/-/import-regex-1.1.0.tgz", + "integrity": "sha512-EblpleIyIdATUKj8ovFojUHyToxgjeKXQgTHZBGZ4cEkbtV21BlO1PSrzZQ6Fei2fgk7uhDeEx656yvPhlRGeA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmmirror.com/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + }, + "node_modules/ip-regex": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/ip-regex/-/ip-regex-1.0.3.tgz", + "integrity": "sha512-HjpCHTuxbR/6jWJroc/VN+npo5j0T4Vv2TAI5qdEHQx7hsL767MeccGFSsLtF694EiZKTSEqgoeU6DtGFCcuqQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", + "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/is-data-descriptor": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", + "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==" + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/istextorbinary": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/istextorbinary/-/istextorbinary-3.3.0.tgz", + "integrity": "sha512-Tvq1W6NAcZeJ8op+Hq7tdZ434rqnMx4CCZ7H0ff83uEloDvVbqAwaMTZcafKGJT0VHkYzuXUiCY4hlXQg6WfoQ==", + "dependencies": { + "binaryextensions": "^2.2.0", + "textextensions": "^3.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-message-util/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-validate/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/joi": { + "version": "17.11.0", + "resolved": "https://registry.npmmirror.com/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmmirror.com/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, + "node_modules/js-beautify": { + "version": "1.14.11", + "resolved": "https://registry.npmmirror.com/js-beautify/-/js-beautify-1.14.11.tgz", + "integrity": "sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.3", + "glob": "^10.3.3", + "nopt": "^7.2.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsc-android": { + "version": "250231.0.0", + "resolved": "https://registry.npmmirror.com/jsc-android/-/jsc-android-250231.0.0.tgz", + "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==" + }, + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==" + }, + "node_modules/jscodeshift": { + "version": "0.14.0", + "resolved": "https://registry.npmmirror.com/jscodeshift/-/jscodeshift-0.14.0.tgz", + "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", + "dependencies": { + "@babel/core": "^7.13.16", + "@babel/parser": "^7.13.16", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", + "@babel/preset-flow": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", + "@babel/register": "^7.13.16", + "babel-core": "^7.0.0-bridge.0", + "chalk": "^4.1.2", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "neo-async": "^2.5.0", + "node-dir": "^0.1.17", + "recast": "^0.21.0", + "temp": "^0.8.4", + "write-file-atomic": "^2.3.0" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.js" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/jscodeshift/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jscodeshift/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jscodeshift/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/just-debounce": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/just-debounce/-/just-debounce-1.1.0.tgz", + "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==" + }, + "node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha512-U/VxvpX4N/rFvPzr3qG5EtLKEnNI0emvIQB3/ecEwv+8GHaUKbIB8vxv1Oai5FAF0d0r7LXHhLLe5K/yChm5GQ==", + "dependencies": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", + "dependencies": { + "invert-kv": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lead": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/lead/-/lead-1.0.0.tgz", + "integrity": "sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow==", + "dependencies": { + "flush-write-stream": "^1.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/less": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dependencies": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/liftoff/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha512-t3N26QR2IdSN+gqSy9Ds9pBu/J1EAFEshKlUHpJG3rvyJOYgcELIxcIeKKfZk7sjOz11cFfzJRsyFry/JyabJQ==", + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha512-rFR6Vpm4HeCK1WPGvjZSJ+7yik8d8PVUdCJx5rT2pogG4Ve/2ZS7kfmO5l5T2o5V2mqlNIfSF5MZlr1+xOoYQQ==" + }, + "node_modules/lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha512-mTzAr1aNAv/i7W43vOR/uD/aJ4ngbtsRaCubp2BfZhlGU/eORUjg/7F6X0orNMdv33JOrdgGybtvMN/po3EWrA==" + }, + "node_modules/lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha512-H94wl5P13uEqlCg7OcNNhMQ8KvWSIyqXzOPusRgHC9DK3o54P6P3xtbXlVbRABG4q5gSmp7EDdJ0MSuW9HX6Mg==" + }, + "node_modules/lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha512-2wlI0JRAGX8WEf4Gm1p/mv/SZ+jLijpj0jyaE/AXeuQphzCgD8ZQW4oSpoN8JAopujOFGU3KMuq7qfHBWlGpjQ==" + }, + "node_modules/lodash._createassigner": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha512-LziVL7IDnJjQeeV95Wvhw6G28Z8Q6da87LWKOPWmzBLv4u6FAT/x5v00pyGW0u38UoogNF2JnD3bGgZZDaNEBw==", + "dependencies": { + "lodash._bindcallback": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "node_modules/lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmmirror.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==" + }, + "node_modules/lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmmirror.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha512-De+ZbrMu6eThFti/CSzhRvTKMgQToLxbij58LMfM8JnYDNSOjkjTCIaa8ixglOeGh2nyPlakbt5bJWJ7gvpYlQ==" + }, + "node_modules/lodash._isnative": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/lodash._isnative/-/lodash._isnative-2.4.1.tgz", + "integrity": "sha512-BOlKGKNHhCHswGOWtmVb5zBygyxN7EmTuzVOSQI6QSoGhG+kvv71gICFS1TBpnqvT1n53txK8CDK3u5D2/GZxQ==" + }, + "node_modules/lodash._objecttypes": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", + "integrity": "sha512-XpqGh1e7hhkOzftBfWE7zt+Yn9mVHFkDhicVttvKLsoCMLVVL+xTQjfjB4X4vtznauxv0QZ5ZAeqjvat0dh62Q==" + }, + "node_modules/lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha512-Sjlavm5y+FUVIF3vF3B75GyXrzsfYV8Dlv3L4mEpuB9leg8N6yf/7rU06iLPx9fY0Mv3khVp9p7Dx0mGV6V5OQ==" + }, + "node_modules/lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha512-OrPwdDc65iJiBeUe5n/LIjd7Viy99bKwDdk7Z5ljfZg0uFRFlfQaCy9tZ4YMAag9WAZmlVpe1iZrkIMMSMHD3w==" + }, + "node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==" + }, + "node_modules/lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==" + }, + "node_modules/lodash._shimkeys": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", + "integrity": "sha512-lBrglYxLD/6KAJ8IEa5Lg+YHgNAL7FyKqXg4XOUI+Du/vtniLs1ZqS+yHNKPkK54waAgkdUnDOYaWf+rv4B+AA==", + "dependencies": { + "lodash._objecttypes": "~2.4.1" + } + }, + "node_modules/lodash.assign": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/lodash.assign/-/lodash.assign-3.2.0.tgz", + "integrity": "sha512-/VVxzgGBmbphasTg51FrztxQJ/VgAUpol6zmJuSVSGcNg4g7FA4z7rQV8Ovr9V3vFBNWZhvKWHfpAytjTVUfFA==", + "dependencies": { + "lodash._baseassign": "^3.0.0", + "lodash._createassigner": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.defaults": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/lodash.defaults/-/lodash.defaults-3.1.2.tgz", + "integrity": "sha512-X7135IXFQt5JDFnYxOVAzVz+kFvwDn3N8DJYf+nrz/mMWEuSu7+OL6rWqsk3+VR1T4TejFCSu5isBJOLSID2bg==", + "dependencies": { + "lodash.assign": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "node_modules/lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha512-n1PZMXgaaDWZDSvuNZ/8XOcYO2hOKDqZel5adtR30VKQAtoWs/5AOeFA0vPV8moiPzlqe7F4cP2tzpFewQyelQ==", + "dependencies": { + "lodash._root": "^3.0.0" + } + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, + "node_modules/lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==" + }, + "node_modules/lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha512-sTebg2a1PoicYEZXD5PBdQcTlIJ6hUslrlWr7iV0O7n+i4596s2NQ9I5CaZ5FbXSfya/9WQsrYLANUJv9paYVA==", + "dependencies": { + "lodash._objecttypes": "~2.4.1" + } + }, + "node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ==", + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha512-L4/arjjuq4noiUJpt3yS6KIKDtJwNe2fIYgMqyYYKoeIfV1iEqvPwhCx23o+R9dzouGihDAPN1dTIRWa7zk8tw==" + }, + "node_modules/lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "node_modules/lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dependencies": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/logkitty/-/logkitty-0.7.1.tgz", + "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", + "dependencies": { + "ansi-fragments": "^0.2.1", + "dayjs": "^1.8.15", + "yargs": "^15.1.0" + }, + "bin": { + "logkitty": "bin/logkitty.js" + } + }, + "node_modules/logkitty/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/logkitty/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/logkitty/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/logkitty/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/logkitty/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "node_modules/logkitty/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, + "node_modules/logkitty/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/logkitty/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "node_modules/make-error-cause": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/make-error-cause/-/make-error-cause-1.2.2.tgz", + "integrity": "sha512-4TO2Y3HkBnis4c0dxhAgD/jprySYLACf7nwN6V0HAHDx59g12WlRpUmFy1bRHamjGUEEBrEvCq6SUpsEE2lhUg==", + "dependencies": { + "make-error": "^1.2.0" + } + }, + "node_modules/make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/make-iterator/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==" + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/marky": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==" + }, + "node_modules/matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", + "dependencies": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/matchdep/node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/matchdep/node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/matchdep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/metro": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro/-/metro-0.80.1.tgz", + "integrity": "sha512-yp0eLYFY+5seXr7KR1fe61eDL4Qf5dvLS6dl1eKn4DPKgROC9A4nTsulHdMy2ntXWgjnAZRJBDPHuh3tAi4/nQ==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "accepts": "^1.3.7", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "denodeify": "^1.2.1", + "error-stack-parser": "^2.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.17.1", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.6.3", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.80.1", + "metro-cache": "0.80.1", + "metro-cache-key": "0.80.1", + "metro-config": "0.80.1", + "metro-core": "0.80.1", + "metro-file-map": "0.80.1", + "metro-minify-terser": "0.80.1", + "metro-resolver": "0.80.1", + "metro-runtime": "0.80.1", + "metro-source-map": "0.80.1", + "metro-symbolicate": "0.80.1", + "metro-transform-plugins": "0.80.1", + "metro-transform-worker": "0.80.1", + "mime-types": "^2.1.27", + "node-fetch": "^2.2.0", + "nullthrows": "^1.1.1", + "rimraf": "^3.0.2", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "strip-ansi": "^6.0.0", + "throat": "^5.0.0", + "ws": "^7.5.1", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-babel-transformer": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-babel-transformer/-/metro-babel-transformer-0.80.1.tgz", + "integrity": "sha512-8mFluLGyOKzhedSAFANCe1cyT2fBlt1+tl0dqlcJI6OCP/V0I22bNFlyogWzseOjVTd3c0iEAbRXioZOUGOMzQ==", + "dependencies": { + "@babel/core": "^7.20.0", + "hermes-parser": "0.17.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.17.1", + "resolved": "https://registry.npmmirror.com/hermes-estree/-/hermes-estree-0.17.1.tgz", + "integrity": "sha512-EdUJms+eRE40OQxysFlPr1mPpvUbbMi7uDAKlScBw8o3tQY22BZ5yx56OYyp1bVaBm+7Cjc3NQz24sJEFXkPxg==" + }, + "node_modules/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.17.1", + "resolved": "https://registry.npmmirror.com/hermes-parser/-/hermes-parser-0.17.1.tgz", + "integrity": "sha512-yErtFLMEL6490fFJPurNn23OI2ciGAtaUfKUg9VPdcde9CmItCjOVQkJt1Xzawv5kuRzeIx0RE2E2Q9TbIgdzA==", + "dependencies": { + "hermes-estree": "0.17.1" + } + }, + "node_modules/metro-cache": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-cache/-/metro-cache-0.80.1.tgz", + "integrity": "sha512-pAYrlPCnomv7EQi08YSeoeF7YL3/4S3JzNn+nVp8e7AIOekO6Hf9j/GPRKfIQwll+os5bE9qFa++NPPmD59IeQ==", + "dependencies": { + "metro-core": "0.80.1", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-cache-key": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-cache-key/-/metro-cache-key-0.80.1.tgz", + "integrity": "sha512-Hj2CWFVy11dEa7iNoy2fI14kD6DiFUD7houGTnFy9esCAm3y/hedciMXg4+1eihz+vtfhPWUIu+ZW/sXeIQkFQ==", + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-config": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-config/-/metro-config-0.80.1.tgz", + "integrity": "sha512-ADbPLfMAe68CJGwu6vM0cXImfME0bauLK8P98mQbiAP6xLYVehCdeXEWSe9plVWhzpPLNemSr1AlTvPTMdl3Bw==", + "dependencies": { + "connect": "^3.6.5", + "cosmiconfig": "^5.0.5", + "jest-validate": "^29.6.3", + "metro": "0.80.1", + "metro-cache": "0.80.1", + "metro-core": "0.80.1", + "metro-runtime": "0.80.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-core": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-core/-/metro-core-0.80.1.tgz", + "integrity": "sha512-f2Kav0/467YBG0DGAEX6+EQoYcUK+8vXIrEHQSkxCPXTjFcyppXUt2O6SDHMlL/Z5CGpd4uK1c/byXEfImJJdA==", + "dependencies": { + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.80.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-file-map": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-file-map/-/metro-file-map-0.80.1.tgz", + "integrity": "sha512-Z00OaxlVx1Ynr3r3bZwgI9RXaimh1evTgofuk5TeYC5LEKWcAVr7QU0cGbjfhXa/kzD8iFFYPbDBENOXc398XQ==", + "dependencies": { + "anymatch": "^3.0.3", + "debug": "^2.2.0", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.6.3", + "micromatch": "^4.0.4", + "node-abort-controller": "^3.1.1", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/metro-file-map/node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/metro-file-map/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/metro-minify-terser": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-minify-terser/-/metro-minify-terser-0.80.1.tgz", + "integrity": "sha512-LfX3n895J6MsyiQkLz2SYcKVmZA1ag0NfYDyQapdnOd/oZmkdSu5jUWt0IjiohRLqKSnvyDp00OdQDRfhD3S8g==", + "dependencies": { + "terser": "^5.15.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-resolver": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-resolver/-/metro-resolver-0.80.1.tgz", + "integrity": "sha512-NuVTx+eplveM8mNybsCQ9BrATGw7lXhfEIvCa7gz6eMcKOQ6RBzwUXWMYKehw8KL4eIkNOHzdczAiGTRuhzrQg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-runtime": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-runtime/-/metro-runtime-0.80.1.tgz", + "integrity": "sha512-RQ+crdwbC4oUYzWom8USCvJWEfFyIuQAeV0bVcNvbpaaz3Q4imXSINJkjDth37DHnxUlhNhEeAcRG6JQIO1QeA==", + "dependencies": { + "@babel/runtime": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-source-map": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-source-map/-/metro-source-map-0.80.1.tgz", + "integrity": "sha512-RoVaBdS44H68WY3vaO+s9/wshypPy8gKgcbND+A4FRxVsKM3+PI2pRoaAk4lTshgbmmXUuBZADzXdCz4F2JmnQ==", + "dependencies": { + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "invariant": "^2.2.4", + "metro-symbolicate": "0.80.1", + "nullthrows": "^1.1.1", + "ob1": "0.80.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-symbolicate": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-symbolicate/-/metro-symbolicate-0.80.1.tgz", + "integrity": "sha512-HxIHH/wLPyO9pZTmIfvCG/63n8UDTLjHzcWPMRUiLOc0cHa/NI2ewtik1VK2Lzm3swvU8EfD9XXJ//jEnIlhIg==", + "dependencies": { + "invariant": "^2.2.4", + "metro-source-map": "0.80.1", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-symbolicate/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-symbolicate/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/metro-transform-plugins": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-transform-plugins/-/metro-transform-plugins-0.80.1.tgz", + "integrity": "sha512-sJkzY9WJ9p7t3TrvNuIxW/6z4nQZC1pN3nJl4eQmE2lmHBqEMeZr/83DyTnf9Up86abQAXHVZmG5JzXrq7Kb5g==", + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-transform-worker": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/metro-transform-worker/-/metro-transform-worker-0.80.1.tgz", + "integrity": "sha512-SkX9JBQGbNkzJ2oF7sAi8Nbc0KRLj8Rus9Z4kPh++JCTNqEwsZV5z27ksr9I9EGbqL2/qfUrDZJo1OwozX6dhw==", + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/types": "^7.20.0", + "metro": "0.80.1", + "metro-babel-transformer": "0.80.1", + "metro-cache": "0.80.1", + "metro-cache-key": "0.80.1", + "metro-source-map": "0.80.1", + "metro-transform-plugins": "0.80.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/metro/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/metro/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "node_modules/metro/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/metro/node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/metro/node_modules/hermes-estree": { + "version": "0.17.1", + "resolved": "https://registry.npmmirror.com/hermes-estree/-/hermes-estree-0.17.1.tgz", + "integrity": "sha512-EdUJms+eRE40OQxysFlPr1mPpvUbbMi7uDAKlScBw8o3tQY22BZ5yx56OYyp1bVaBm+7Cjc3NQz24sJEFXkPxg==" + }, + "node_modules/metro/node_modules/hermes-parser": { + "version": "0.17.1", + "resolved": "https://registry.npmmirror.com/hermes-parser/-/hermes-parser-0.17.1.tgz", + "integrity": "sha512-yErtFLMEL6490fFJPurNn23OI2ciGAtaUfKUg9VPdcde9CmItCjOVQkJt1Xzawv5kuRzeIx0RE2E2Q9TbIgdzA==", + "dependencies": { + "hermes-estree": "0.17.1" + } + }, + "node_modules/metro/node_modules/image-size": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/image-size/-/image-size-1.0.2.tgz", + "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/metro/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/metro/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/metro/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/metro/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/micromatch/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/micromatch/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/micromatch/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha512-7ZxrUybYv9NonoXgwoOqtStIu18D1c3eFZj27hqgf5kBrBF8Q+tE8V0MW8dKM5QLkQPh1JhhbKgHLY9kifov4Q==", + "dependencies": { + "duplexer2": "0.0.2" + } + }, + "node_modules/mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmmirror.com/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "node_modules/nan": { + "version": "2.18.0", + "resolved": "https://registry.npmmirror.com/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "optional": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmmirror.com/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/nanomatch/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/nocache": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/nocache/-/nocache-3.0.4.tgz", + "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, + "node_modules/node-dir": { + "version": "0.1.17", + "resolved": "https://registry.npmmirror.com/node-dir/-/node-dir-0.1.17.tgz", + "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", + "dependencies": { + "minimatch": "^3.0.2" + }, + "engines": { + "node": ">= 0.10.5" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/nopt": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/nopt/-/nopt-7.2.0.tgz", + "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dependencies": { + "once": "^1.3.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ob1": { + "version": "0.80.1", + "resolved": "https://registry.npmmirror.com/ob1/-/ob1-0.80.1.tgz", + "integrity": "sha512-o9eYflOo+QnbC/k9GYQuAy90zOGQ/OBgrjlIeW6VrKhevSxth83JSdEvKuKaV7SMGJVQhSY3Zp8eGa3g0rLP0A==", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha512-naLhxxpUESbNkRqc35oQ2scZSJueHGQNUfMW/0U37IgN6tE2dgDWg3whf+NEliy3F/QysrO48XKUz/nGPe+AQw==", + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/open": { + "version": "6.4.0", + "resolved": "https://registry.npmmirror.com/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/open/node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmmirror.com/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw==", + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==", + "dependencies": { + "lcid": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-import": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/parse-import/-/parse-import-2.0.0.tgz", + "integrity": "sha512-c59vdx1LiQT+majNKMyfFLrNMAVS9U1bychTv3CEuxbKspgnVTrzLRtgtfCWyAmTuFAxQVSJFasVv8svJLksIg==", + "dependencies": { + "get-imports": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/plugin-error/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/plugin-error/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/plugin-error/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.4.32", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/pretty-format/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmmirror.com/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/pretty-format/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/pretty-format/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pretty-format/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "optional": true + }, + "node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmmirror.com/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-devtools-core": { + "version": "4.28.5", + "resolved": "https://registry.npmmirror.com/react-devtools-core/-/react-devtools-core-4.28.5.tgz", + "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-devtools-core/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/react-native": { + "version": "0.73.0", + "resolved": "https://registry.npmmirror.com/react-native/-/react-native-0.73.0.tgz", + "integrity": "sha512-ya7wu/L8BeATv2rtXZDToYyD9XuTTDCByi8LvJGr6GKSXcmokkCRMGAiTEZfPkq7+nhVmbasjtoAJDuMRYfudQ==", + "dependencies": { + "@jest/create-cache-key-function": "^29.6.3", + "@react-native-community/cli": "12.1.1", + "@react-native-community/cli-platform-android": "12.1.1", + "@react-native-community/cli-platform-ios": "12.1.1", + "@react-native/assets-registry": "^0.73.1", + "@react-native/codegen": "^0.73.2", + "@react-native/community-cli-plugin": "^0.73.10", + "@react-native/gradle-plugin": "^0.73.4", + "@react-native/js-polyfills": "^0.73.1", + "@react-native/normalize-colors": "^0.73.2", + "@react-native/virtualized-lists": "^0.73.3", + "abort-controller": "^3.0.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "base64-js": "^1.5.1", + "deprecated-react-native-prop-types": "^5.0.0", + "event-target-shim": "^5.0.1", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "jest-environment-node": "^29.6.3", + "jsc-android": "^250231.0.0", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.80.0", + "metro-source-map": "^0.80.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1", + "pretty-format": "^26.5.2", + "promise": "^8.3.0", + "react-devtools-core": "^4.27.7", + "react-refresh": "^0.14.0", + "react-shallow-renderer": "^16.15.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.24.0-canary-efb381bbf-20230505", + "stacktrace-parser": "^0.1.10", + "whatwg-fetch": "^3.0.0", + "ws": "^6.2.2", + "yargs": "^17.6.2" + }, + "bin": { + "react-native": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "18.2.0" + } + }, + "node_modules/react-native/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/react-native/node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/react-native/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/react-native/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/react-redux": { + "version": "9.0.4", + "resolved": "https://registry.npmmirror.com/react-redux/-/react-redux-9.0.4.tgz", + "integrity": "sha512-9J1xh8sWO0vYq2sCxK2My/QO7MzUMRi3rpiILP/+tDr8krBHixC6JMM17fMK88+Oh3e4Ae6/sHIhNBgkUivwFA==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmmirror.com/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/readdirp/node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/readdirp/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readline": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/readline/-/readline-1.3.0.tgz", + "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==" + }, + "node_modules/recast": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/recast/-/recast-0.21.5.tgz", + "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", + "dependencies": { + "ast-types": "0.15.2", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/redux": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/redux/-/redux-5.0.0.tgz", + "integrity": "sha512-blLIYmYetpZMET6Q6uCY7Jtl/Im5OBldy+vNPauA8vvsdqyt66oep4EUpAMWNHauTC6xa9JuRPhRB72rY82QGA==" + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmmirror.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dependencies": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA==", + "dependencies": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-bom-stream/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==" + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha512-CHPV/GAglbIB1tnQgaiysb8H2yCy8WQ7lcEwQ/eT+kLj0QHV8LnJW0zpqpE7RSkrMSRoa+EBoag86clf7WAgSg==", + "dependencies": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/replacestream": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/replacestream/-/replacestream-4.0.3.tgz", + "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", + "dependencies": { + "escape-string-regexp": "^1.0.3", + "object-assign": "^4.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/require-dir": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/require-dir/-/require-dir-1.2.0.tgz", + "integrity": "sha512-LY85DTSu+heYgDqq/mK+7zFHWkttVNRXC9NKcKGyuGLdlsfbjEPrIEYdCVrx6hqnJb+xSu3Lzaoo8VnmOhhjNA==", + "engines": { + "node": "*" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-dot-file": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/require-dot-file/-/require-dot-file-0.4.0.tgz", + "integrity": "sha512-pMe/T7+uFi2NMYsxuQtTh9n/UKD13HAHeDOk7KuP2pr7aKi5aMhvkbGD4IeoJKjy+3vdIUy8ggXYWzlZTL5FWA==" + }, + "node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A==", + "dependencies": { + "value-or-function": "^3.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "deprecated": "https://github.com/lydell/resolve-url#deprecated" + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmmirror.com/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rework": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/rework/-/rework-1.0.1.tgz", + "integrity": "sha512-eEjL8FdkdsxApd0yWVZgBGzfCQiT8yqSc2H1p4jpZpQdtz7ohETiDMoje5PlM8I9WgkqkreVxFUKYOiJdVWDXw==", + "dependencies": { + "convert-source-map": "^0.3.3", + "css": "^2.0.0" + } + }, + "node_modules/rework-import": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/rework-import/-/rework-import-2.1.0.tgz", + "integrity": "sha512-ufvoQX6cDhrqYc8ZXvJ+6FqimwyI4qn8cH1ypAiS9Mn41iVPN/9RGwRvscBtUEkHA09w8voTIakRJKslgWcTEQ==", + "dependencies": { + "css": "^2.0.0", + "globby": "^2.0.0", + "parse-import": "^2.0.0", + "url-regex": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rework-import/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rework-import/node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmmirror.com/glob/-/glob-5.0.15.tgz", + "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rework-import/node_modules/globby": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/globby/-/globby-2.1.0.tgz", + "integrity": "sha512-CqRID2dMaN4Zi9PANiQHhmKaGu7ZASehBLnaDogjR9L3L1EqAGFhflafT0IrSN/zm9xFk+KMTXZCN8pUYOiO/Q==", + "dependencies": { + "array-union": "^1.0.1", + "async": "^1.2.1", + "glob": "^5.0.3", + "object-assign": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rework-import/node_modules/object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rework-plugin-function": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/rework-plugin-function/-/rework-plugin-function-1.0.2.tgz", + "integrity": "sha512-kyIphbC2Kuc3iFz1CSAQ5zmt4o/IHquhO+uG0kK0FQTjs4Z5eAxrqmrv3rZMR1KXa77SesaW9KwKyfbYoLMEqw==", + "dependencies": { + "rework-visit": "^1.0.0" + } + }, + "node_modules/rework-plugin-url": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/rework-plugin-url/-/rework-plugin-url-1.1.0.tgz", + "integrity": "sha512-qlAhbJKfEK59jAPQppIn8bNXffW1INlaJZaXdX/ZLs/CzZSnn38Y0wESQ3tjOwRsDbPEUHN2XJ3ZgueDaaCC0A==", + "dependencies": { + "rework-plugin-function": "^1.0.0" + } + }, + "node_modules/rework-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/rework-visit/-/rework-visit-1.0.0.tgz", + "integrity": "sha512-W6V2fix7nCLUYX1v6eGPrBOZlc03/faqzP4sUxMAJMBMOPYhfV/RyLegTufn5gJKaOITyi+gvf0LXDZ9NzkHnQ==" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rtlcss": { + "version": "3.5.0", + "resolved": "https://registry.npmmirror.com/rtlcss/-/rtlcss-3.5.0.tgz", + "integrity": "sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A==", + "dependencies": { + "find-up": "^5.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.3.11", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "optional": true + }, + "node_modules/scheduler": { + "version": "0.24.0-canary-efb381bbf-20230505", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", + "integrity": "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha512-Ny/iyOzSSa8M5ML46IAx3iXc6tfOsYU2R4AXi2UpHk60Zrgyq6eqPj/xiOfS0rRl/iiQ/rdJkVjw/5cdUyCntQ==", + "dependencies": { + "sver-compat": "^1.5.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmmirror.com/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallow-clone/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmmirror.com/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmmirror.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated" + }, + "node_modules/sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.16", + "resolved": "https://registry.npmmirror.com/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==" + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmmirror.com/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" + }, + "node_modules/stacktrace-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmmirror.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", + "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/stat-mode": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/stat-mode/-/stat-mode-0.2.2.tgz", + "integrity": "sha512-o+7DC0OM5Jt3+gratXXqfXf62V/CBoqQbT7Kp7jCxTYW2PLOB2/ZSGIfm9T5/QZe1Vw1MCbu6DoB6JnhVtxcJw==" + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==" + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom-buf": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", + "integrity": "sha512-1sUIL1jck0T1mhOLP2c696BIznzT525Lkub+n4jjMHjhjhoAQA6Ye659DxdlZBr0aLDMQoTxKIpnlqxgtwjsuQ==", + "dependencies": { + "is-utf8": "^0.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom-stream/-/strip-bom-stream-3.0.0.tgz", + "integrity": "sha512-2di6sulSHfspbuEJHwwF6vzwijA4uaKsKYtviRQsJsOdxxb6yexiDcZFQ5oY10J50YxmCdHn/1sQmxDKbrGOVw==", + "dependencies": { + "first-chunk-stream": "^2.0.0", + "strip-bom-buf": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, + "node_modules/sudo-prompt": { + "version": "9.2.1", + "resolved": "https://registry.npmmirror.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz", + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==" + }, + "node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha512-aFTHfmjwizMNlNE6dsGmoAM4lHjL0CyiobWaFiXWSlD7cIxshW422Nb8KbXCmR6z+0ZEPY+daXJrDyh/vuwTyg==", + "dependencies": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/temp": { + "version": "0.8.4", + "resolved": "https://registry.npmmirror.com/temp/-/temp-0.8.4.tgz", + "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "dependencies": { + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/temp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ternary-stream": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ternary-stream/-/ternary-stream-2.1.1.tgz", + "integrity": "sha512-j6ei9hxSoyGlqTmoMjOm+QNvUKDOIY6bNl4Uh1lhBvl6yjPW2iLqxDUYyfDPZknQ4KdRziFl+ec99iT4l7g0cw==", + "dependencies": { + "duplexify": "^3.5.0", + "fork-stream": "^0.0.4", + "merge-stream": "^1.0.0", + "through2": "^2.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/ternary-stream/node_modules/merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha512-e6RM36aegd4f+r8BZCcYXlO2P3H6xbUM6ktL2Xmf45GAOit9bI4z6/3VU7JwllVO1L7u0UDSg/EhzQ5lmMLolA==", + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/ternary-stream/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/terser": { + "version": "5.26.0", + "resolved": "https://registry.npmmirror.com/terser/-/terser-5.26.0.tgz", + "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/textextensions": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/textextensions/-/textextensions-3.3.0.tgz", + "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dependencies": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "node_modules/through2-filter/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" + }, + "node_modules/to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", + "dependencies": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-regex/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==", + "dependencies": { + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/to-through/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmmirror.com/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undertaker": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", + "dependencies": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/undici": { + "version": "5.28.2", + "resolved": "https://registry.npmmirror.com/undici/-/undici-5.28.2.tgz", + "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dependencies": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated" + }, + "node_modules/url-regex": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/url-regex/-/url-regex-3.2.0.tgz", + "integrity": "sha512-dQ9cJzMou5OKr6ZzfvwJkCq3rC72PNXhqz0v3EIhF4a3Np+ujr100AhUx2cKx5ei3iymoJpJrPB3sVSEMdqAeg==", + "dependencies": { + "ip-regex": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dependencies": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==", + "dependencies": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-sourcemap/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/vinyl-sourcemap/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", + "dependencies": { + "source-map": "^0.5.1" + } + }, + "node_modules/vinyl-sourcemaps-apply/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==" + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmmirror.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/wrench-sui": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/wrench-sui/-/wrench-sui-0.0.3.tgz", + "integrity": "sha512-Y6qzMpcMG9akKnIdUsKzEF/Ht0KQJBP8ETkZj3FcGe93NC71e940WZUP1y+j+hc8Ecx9TyX0GvAWC4yymA88yA==", + "engines": { + "node": ">=0.1.97" + } + }, + "node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmmirror.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/ws": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/yamljs": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/yamljs/-/yamljs-0.3.0.tgz", + "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "dependencies": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + }, + "bin": { + "json2yaml": "bin/json2yaml", + "yaml2json": "bin/yaml2json" + } + }, + "node_modules/yamljs/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/yargs": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-7.1.2.tgz", + "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", + "dependencies": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.1" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-5.0.1.tgz", + "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", + "dependencies": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + } + } + } +} diff --git a/websrc/rubidium-web/package.json b/websrc/rubidium-web/package.json new file mode 100644 index 00000000..4101b620 --- /dev/null +++ b/websrc/rubidium-web/package.json @@ -0,0 +1,13 @@ +{ + "dependencies": { + "@welldone-software/why-did-you-render": "^7.0.1", + "fomantic-ui": "^2.9.3", + "fullscreen": "^1.1.1", + "isomorphic-fetch": "^3.0.0", + "lodash": "^4.17.21", + "react": "^18.2.0", + "react-native": "^0.73.0", + "react-redux": "^9.0.4", + "redux": "^5.0.0" + } +} From af6233d276f1e8036ee01f1adb7497567bb63313 Mon Sep 17 00:00:00 2001 From: Max Qian Date: Sun, 17 Dec 2023 21:50:15 +0800 Subject: [PATCH 8/9] update --- locale/lithium.pot | 4 +- locale/po/en_US.UTF-8/lithium.po | 2 +- src/atom/server/daemon.cpp | 163 ++++++++ src/atom/server/daemon.hpp | 127 ++++++ src/atom/server/message.cpp | 200 +++++++++ src/atom/server/message.hpp | 189 ++------- src/atom/thread/daemon.cpp | 0 src/atom/type/iparams.cpp | 176 ++++++++ src/atom/type/iparams.hpp | 130 ++++++ src/atom/type/small_hashmap.hpp | 122 ++++++ src/atom/type/small_vector.hpp | 213 ++++++++++ src/atom/utils/env.cpp | 248 ++++++++++++ src/atom/utils/env.hpp | 66 +++ src/atom/utils/hash_util.cpp | 672 +++++++++++++++++++++++++++++++ src/atom/utils/hash_util.hpp | 292 ++++++++++++++ src/atom/web/address.cpp | 445 ++++++++++++++++++++ src/atom/web/address.hpp | 171 ++++++++ src/device/device_manager.cpp | 5 + src/device/device_manager.hpp | 16 + 19 files changed, 3086 insertions(+), 155 deletions(-) create mode 100644 src/atom/server/daemon.cpp create mode 100644 src/atom/server/daemon.hpp create mode 100644 src/atom/server/message.cpp create mode 100644 src/atom/thread/daemon.cpp create mode 100644 src/atom/type/iparams.cpp create mode 100644 src/atom/type/iparams.hpp create mode 100644 src/atom/type/small_hashmap.hpp create mode 100644 src/atom/type/small_vector.hpp create mode 100644 src/atom/utils/env.cpp create mode 100644 src/atom/utils/env.hpp create mode 100644 src/atom/utils/hash_util.cpp create mode 100644 src/atom/utils/hash_util.hpp create mode 100644 src/atom/web/address.cpp create mode 100644 src/atom/web/address.hpp diff --git a/locale/lithium.pot b/locale/lithium.pot index 9bcca359..fccccf81 100644 --- a/locale/lithium.pot +++ b/locale/lithium.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: astro_air@126.com\n" -"POT-Creation-Date: 2023-12-15 18:06+0800\n" +"POT-Creation-Date: 2023-12-17 21:01+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: E:/msys64/home/Qrm/Lithium/src/device/device_manager.cpp:162 +#: E:/msys64/home/Qrm/Lithium/src/device/device_manager.cpp:167 #, c++-format msgid "A device with name {} already exists, please choose a different name" msgstr "" diff --git a/locale/po/en_US.UTF-8/lithium.po b/locale/po/en_US.UTF-8/lithium.po index aa7ad4be..2bcef214 100644 --- a/locale/po/en_US.UTF-8/lithium.po +++ b/locale/po/en_US.UTF-8/lithium.po @@ -1,4 +1,4 @@ -#: E:/msys64/home/Qrm/Lithium/src/device/device_manager.cpp:162 +#: E:/msys64/home/Qrm/Lithium/src/device/device_manager.cpp:167 #, c++-format msgid "A device with name {} already exists, please choose a different name" msgstr "" diff --git a/src/atom/server/daemon.cpp b/src/atom/server/daemon.cpp new file mode 100644 index 00000000..88e3b91f --- /dev/null +++ b/src/atom/server/daemon.cpp @@ -0,0 +1,163 @@ +/* + * daemon.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-11-11 + +Description: Daemon thread implementation + +**************************************************/ + +#include "daemon.hpp" + +#include "atom/log/loguru.hpp" + +std::string ProcessInfo::toString() const +{ + std::stringstream ss; + ss << "[ProcessInfo parent_id=" << ProcessInfoMgr::GetInstance()->parent_id + << " main_id=" << ProcessInfoMgr::GetInstance()->main_id + << " parent_start_time=" << Time2Str(ProcessInfoMgr::GetInstance()->parent_start_time) + << " main_start_time=" << Time2Str(ProcessInfoMgr::GetInstance()->main_start_time) + << " restart_count=" << ProcessInfoMgr::GetInstance()->restart_count << "]"; + return ss.str(); +} + +int ProcessInfo::real_start(int argc, char **argv, + std::function main_cb) +{ + ProcessInfoMgr::GetInstance()->main_id = getpid(); + ProcessInfoMgr::GetInstance()->main_start_time = time(0); + return main_cb(argc, argv); +} + +int ProcessInfo::real_daemon(int argc, char **argv, + std::function main_cb) +{ +#ifdef _WIN32 + // Create new process to hide window and console output + STARTUPINFO si = {sizeof(si)}; + PROCESS_INFORMATION pi; + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + + if (!CreateProcess(NULL, GetCommandLine(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) + { + LOG_F(ERROR, "Create daemon process failed"); + return -1; + } + + // Exit parent process + ExitProcess(0); +#else + daemon(1, 0); + ProcessInfoMgr::GetInstance()->parent_id = getpid(); + ProcessInfoMgr::GetInstance()->parent_start_time = time(0); + while (true) + { + pid_t pid = fork(); + if (pid == 0) + { + // Child process returns + ProcessInfoMgr::GetInstance()->main_id = getpid(); + ProcessInfoMgr::GetInstance()->main_start_time = time(0); + LOG_F(INFO, "daemon process start pid={} argv={}", getpid(), argv) + return real_start(argc, argv, main_cb); + } + else if (pid < 0) + { + LOG_F(ERROR, "fork fail return={} errno={} errstr={}", pid, errno, strerror(errno)); + return -1; + } + else + { + // Parent process + // Parent process returns + int status = 0; + waitpid(pid, &status, 0); + + // Terminated abnormally + if (status) + { + if (status == 9) + { + LOG_F(INFO, "daemon process killed pid={}", getpid()); + break; + } + else + { + LOG_F(ERROR, "child crash pid={} status={}", pid, status); + } + } + else + { + LOG_F(INFO, "daemon process restart pid={}", getpid()); + break; + } + + // Restart child process + ProcessInfoMgr::GetInstance()->restart_count += 1; + sleep(g_daemon_restart_interval->getValue()); + } + } +#endif + + return 0; +} + +int ProcessInfo::start_daemon(int argc, char **argv, + std::function main_cb, + bool is_daemon) +{ +#ifdef _WIN32 + if (!is_daemon) + { + ProcessInfoMgr::GetInstance()->parent_id = getpid(); + ProcessInfoMgr::GetInstance()->parent_start_time = time(0); + return real_start(argc, argv, main_cb); + } +#else + if (!is_daemon) + { + ProcessInfoMgr::GetInstance()->parent_id = getpid(); + ProcessInfoMgr::GetInstance()->parent_start_time = time(0); + return real_start(argc, argv, main_cb); + } +#endif + + return real_daemon(argc, argv, main_cb); +} + +int main_cb(int argc, char **argv) +{ + std::cout << "Hello from main_cb!" << std::endl; + return 0; +} + +int main(int argc, char **argv) +{ + ProcessInfo processInfo; + processInfo.start_daemon(argc, argv, std::bind(main_cb, argc, argv), false); + return 0; +} \ No newline at end of file diff --git a/src/atom/server/daemon.hpp b/src/atom/server/daemon.hpp new file mode 100644 index 00000000..e3133991 --- /dev/null +++ b/src/atom/server/daemon.hpp @@ -0,0 +1,127 @@ +/* + * daemon.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-11-11 + +Description: Daemon thread implementation + +**************************************************/ + +#pragma once + +/** + * @file process_info.h + * @brief This file contains the definition of ProcessInfoMgr and ProcessInfo classes for managing process information. + */ + +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +/** + * @brief Converts timestamp to string in format "YYYY-MM-DD HH:MM:SS". + * + * @param timestamp The timestamp to be converted. + * @return The string representation of the timestamp in "YYYY-MM-DD HH:MM:SS" format. + */ +std::string Time2Str(time_t timestamp); + +/** + * @brief Singleton class for managing process information. + */ +class ProcessInfoMgr +{ +public: + /** + * @brief Gets the singleton instance of ProcessInfoMgr. + * + * @return Pointer to the singleton instance of ProcessInfoMgr. + */ + static ProcessInfoMgr *GetInstance(); + + int parent_id = 0; /**< The parent process ID. */ + int main_id = 0; /**< The main process ID. */ + time_t parent_start_time = 0; /**< The start time of the parent process. */ + time_t main_start_time = 0; /**< The start time of the main process. */ + int restart_count = 0; /**< The number of times the main process has been restarted. */ +}; + +/** + * @brief Class for managing process information and starting the process. + */ +class ProcessInfo +{ +public: + /** + * @brief Returns a string representation of the process information. + * + * @return The string representation of the process information. + */ + std::string toString() const; + + /** + * @brief Starts the process without daemonizing it. + * + * @param argc The number of command-line arguments. + * @param argv The array of command-line arguments. + * @param main_cb The callback function to be executed by the main process. + * @return The exit code of the main process. + */ + static int real_start(int argc, char **argv, + std::function main_cb); + + /** + * @brief Starts the process and daemonizes it. + * + * @param argc The number of command-line arguments. + * @param argv The array of command-line arguments. + * @param main_cb The callback function to be executed by the main process. + * @return The exit code of the main process. + */ + static int real_daemon(int argc, char **argv, + std::function main_cb); + + /** + * @brief Starts the process with or without daemonizing it. + * + * @param argc The number of command-line arguments. + * @param argv The array of command-line arguments. + * @param main_cb The callback function to be executed by the main process. + * @param is_daemon Whether to daemonize the process or not. + * @return The exit code of the main process. + */ + int start_daemon(int argc, char **argv, + std::function main_cb, + bool is_daemon); +}; + diff --git a/src/atom/server/message.cpp b/src/atom/server/message.cpp new file mode 100644 index 00000000..f804c14b --- /dev/null +++ b/src/atom/server/message.cpp @@ -0,0 +1,200 @@ +#include "message.hpp" + +#include "atom/utils/time.hpp" + +Message::Message(Type t, const std::string &target, const std::string &origin) + : type_(t), target_(target), origin_(origin),timestamp_(GetChinaTimestampString()) {} + +// TextMessage class +class TextMessage : public Message +{ +public: + explicit TextMessage(std::string text) : Message(Type::kText), text_(std::move(text)) {} + const std::string &text() const { return text_; } + void print() const override { std::cout << "Text Message: " << text_ << '\n'; } + +private: + std::string text_; +}; + +// NumberMessage class +class NumberMessage : public Message +{ +public: + explicit NumberMessage(double number) : Message(Type::kNumber), number_(number) {} + double number() const { return number_; } + void print() const override { std::cout << "Number Message: " << number_ << '\n'; } + +private: + double number_; +}; + +// StructuredDataMessage class +class StructuredDataMessage : public Message +{ +public: + explicit StructuredDataMessage(std::vector data) : Message(Type::kStructuredData), data_(std::move(data)) {} + const std::vector &data() const { return data_; } + void print() const override + { + std::cout << "Structured Data Message: "; + for (const auto &num : data_) + { + std::cout << num << ' '; + } + std::cout << '\n'; + } + +private: + std::vector data_; +}; + +// CustomMessage1 class +class CustomMessage1 : public Message +{ +public: + explicit CustomMessage1(std::string data) : Message(Type::kCustom1), data_(std::move(data)) {} + const std::string &data() const { return data_; } + void print() const override { std::cout << "Custom Message 1: " << data_ << '\n'; } + +private: + std::string data_; +}; + +// CustomMessage2 class +class CustomMessage2 : public Message +{ +public: + explicit CustomMessage2(int value) : Message(Type::kCustom2), value_(value) {} + int value() const { return value_; } + void print() const override { std::cout << "Custom Message 2: " << value_ << '\n'; } + +private: + int value_; +}; + +// Message factory function +template +std::unique_ptr MakeMessage(Args &&...args) +{ + return std::make_unique(std::forward(args)...); +} + +// VariantMessage class, using std::unique_ptr to store different message types +class VariantMessage +{ +public: + VariantMessage() : msg_(std::make_unique("")) {} + + template + void Set(T message) + { + msg_ = std::make_unique(std::move(message)); + } + + template + const T &Get() const + { + return static_cast(*msg_); + } + + std::optional type() const + { + if (msg_) + { + return msg_->type(); + } + return std::nullopt; + } + + void reset() + { + msg_ = std::make_unique(""); + } + +private: + std::unique_ptr msg_; +}; + +int main() +{ + // Create different types of messages using the factory function + auto text_msg = MakeMessage("Hello, world!"); + auto number_msg = MakeMessage(3.14); + auto structured_data_msg = MakeMessage(std::vector{1, 2, 3}); + auto custom_msg1 = MakeMessage("Custom message 1"); + auto custom_msg2 = MakeMessage(42); + + // Store messages in VariantMessage + VariantMessage variant_msg; + variant_msg.Set(std::move(*text_msg)); + + // Print the message based on its type + if (auto type = variant_msg.type()) + { + switch (*type) + { + case Message::Type::kText: + variant_msg.Get().print(); + break; + case Message::Type::kNumber: + variant_msg.Get().print(); + break; + case Message::Type::kStructuredData: + variant_msg.Get().print(); + break; + case Message::Type::kCustom1: + variant_msg.Get().print(); + break; + case Message::Type::kCustom2: + variant_msg.Get().print(); + break; + default: + std::cout << "Unknown Message Type\n"; + break; + } + } + else + { + std::cout << "No Message Set\n"; + } + + // Add more messages to VariantMessage + variant_msg.Set(std::move(*number_msg)); + variant_msg.Set(std::move(*structured_data_msg)); + + variant_msg.reset(); + variant_msg.Set(std::move(*structured_data_msg)); + + // Print the new message based on its type + if (auto type = variant_msg.type()) + { + switch (*type) + { + case Message::Type::kText: + variant_msg.Get().print(); + break; + case Message::Type::kNumber: + variant_msg.Get().print(); + break; + case Message::Type::kStructuredData: + variant_msg.Get().print(); + break; + case Message::Type::kCustom1: + variant_msg.Get().print(); + break; + case Message::Type::kCustom2: + variant_msg.Get().print(); + break; + default: + std::cout << "Unknown Message Type\n"; + break; + } + } + else + { + std::cout << "No Message Set\n"; + } + + return 0; +} diff --git a/src/atom/server/message.hpp b/src/atom/server/message.hpp index c7f811e0..c1930141 100644 --- a/src/atom/server/message.hpp +++ b/src/atom/server/message.hpp @@ -1,223 +1,108 @@ -#include +#pragma once + #include -#include #include #include +#include -// 基类 Message +// Base class Message class Message { public: virtual ~Message() = default; - // 消息类型枚举 + // Message type enumeration enum class Type { kText, kNumber, kBoolean, - kStructuredData, - kCustom1, - kCustom2, + kSwitch, + kAny, kMaxType }; Type type() const { return type_; } + // Common operations that can be performed on all message types + virtual void serialize() const = 0; + protected: - explicit Message(Type t) : type_(t) {} + explicit Message(Type t, const std::string &target, const std::string &origin); private: Type type_; + std::string_view target_; + std::string_view origin_; - std::string_view timestamp; - std::string_view sender; - std::string_view target; + std::string_view timestamp_; + double api_version_{1.0}; }; -// TextMessage 类 +// TextMessage class class TextMessage : public Message { public: - explicit TextMessage(std::string text) : Message(Type::kText), text_(std::move(text)) {} - const std::string &text() const { return text_; } + explicit TextMessage(std::string text,const std::string &target, const std::string &origin); + void serialize() const override; private: std::string text_; }; -// NumberMessage 类 +// NumberMessage class class NumberMessage : public Message { public: explicit NumberMessage(double number) : Message(Type::kNumber), number_(number) {} - double number() const { return number_; } + void serialize() const override; private: double number_; }; -// StructuredDataMessage 类 +// StructuredDataMessage class class StructuredDataMessage : public Message { public: explicit StructuredDataMessage(std::vector data) : Message(Type::kStructuredData), data_(std::move(data)) {} - const std::vector &data() const { return data_; } + void serialize() const override; private: std::vector data_; }; -// CustomMessage1 类 -class CustomMessage1 : public Message +class SwitchMessage : public Message { public: - explicit CustomMessage1(std::string data) : Message(Type::kCustom1), data_(std::move(data)) {} - const std::string &data() const { return data_; } + explicit SwitchMessage(std::string name, std::string value) : Message(Type::kSwitch), name_(std::move(name)), value_(std::move(value)) {} + void serialize() const override; private: - std::string data_; + std::string name_; + std::string value_; }; -// CustomMessage2 类 -class CustomMessage2 : public Message +class AnyMessage : public Message { public: - explicit CustomMessage2(int value) : Message(Type::kCustom2), value_(value) {} - int value() const { return value_; } + explicit AnyMessage(std::string data) : Message(Type::kAny), data_(std::move(data)) {} + void serialize() const override; private: - int value_; + std::string data_; }; -// Message 类的工厂函数 +// Message factory function template -std::unique_ptr MakeMessage(Args &&...args) +std::unique_ptr MakeUniqueMessage(Args &&...args) { return std::make_unique(std::forward(args)...); } -// VariantMessage 类,使用 std::variant 存储各种消息类型 -class VariantMessage -{ -public: - VariantMessage() : msg_(TextMessage("")) {} - template - void Set(T message) - { - msg_ = std::move(message); - } - - template - const T &Get() const - { - return std::get(msg_); - } - - Message::Type type() const - { - try - { - return static_cast(msg_.index()); - } - catch (const std::bad_variant_access &) - { - return Message::Type::kMaxType; - } - } - -private: - std::variant< - TextMessage, - NumberMessage, - StructuredDataMessage, - CustomMessage1, - CustomMessage2> - msg_; -}; - -int main() +// Message factory function +template +std::unique_ptr MakeSharedMessage(Args &&...args) { - // 使用工厂函数创建不同类型的消息 - auto text_msg = MakeMessage("Hello, world!"); - auto number_msg = MakeMessage(3.14); - auto structured_data_msg = MakeMessage(std::vector{1, 2, 3}); - auto custom_msg1 = MakeMessage("Custom message 1"); - auto custom_msg2 = MakeMessage(42); - - // 将消息存储在 VariantMessage 中 - VariantMessage variant_msg; - variant_msg.Set(std::move(*text_msg)); - - // 通过 type() 函数获取消息类型,并根据类型进行相应操作 - switch (variant_msg.type()) - { - case Message::Type::kText: - std::cout << "Text Message: " << variant_msg.Get().text() << '\n'; - break; - - case Message::Type::kNumber: - std::cout << "Number Message: " << variant_msg.Get().number() << '\n'; - break; - - case Message::Type::kStructuredData: - std::cout << "Structured Data Message: "; - for (const auto &num : variant_msg.Get().data()) - { - std::cout << num << ' '; - } - std::cout << '\n'; - break; - - case Message::Type::kCustom1: - std::cout << "Custom Message 1: " << variant_msg.Get().data() << '\n'; - break; - - case Message::Type::kCustom2: - std::cout << "Custom Message 2: " << variant_msg.Get().value() << '\n'; - break; - - default: - std::cout << "Unknown Message Type\n"; - break; - } - - // 添加更多的消息类型 - variant_msg.Set(std::move(*number_msg)); - variant_msg.Set(std::move(*structured_data_msg)); - - // 通过 type() 函数获取消息类型,并根据类型进行相应操作 - switch (variant_msg.type()) - { - case Message::Type::kText: - std::cout << "Text Message: " << variant_msg.Get().text() << '\n'; - break; - - case Message::Type::kNumber: - std::cout << "Number Message: " << variant_msg.Get().number() << '\n'; - break; - - case Message::Type::kStructuredData: - std::cout << "Structured Data Message: "; - for (const auto &num : variant_msg.Get().data()) - { - std::cout << num << ' '; - } - std::cout << '\n'; - break; - - case Message::Type::kCustom1: - std::cout << "Custom Message 1: " << variant_msg.Get().data() << '\n'; - break; - - case Message::Type::kCustom2: - std::cout << "Custom Message 2: " << variant_msg.Get().value() << '\n'; - break; - - default: - std::cout << "Unknown Message Type\n"; - break; - } - - return 0; + return std::make_shared(std::forward(args)...); } diff --git a/src/atom/thread/daemon.cpp b/src/atom/thread/daemon.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/atom/type/iparams.cpp b/src/atom/type/iparams.cpp new file mode 100644 index 00000000..6efc9eff --- /dev/null +++ b/src/atom/type/iparams.cpp @@ -0,0 +1,176 @@ +/* + * iparams.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-17 + +Description: Special Parameters Library for Atom + +**************************************************/ + +#include "iparams.hpp" + +#include +#include + +#include +#include + +using json = nlohmann::json; + +bool IParams::serialize(const std::string &filename) const +{ + json jsonData; + for (const auto §ion : data) + { + json sectionData; + for (const auto &entry : section.second) + { + sectionData[entry.first] = entry.second; + } + jsonData[section.first] = sectionData; + } + + std::ofstream file(filename); + if (!file) + { + LOG_F(ERROR, "Failed to open file for writing: {}", filename); + return false; + } + + file << jsonData.dump(4); + file.close(); + return true; +} + +bool IParams::deserialize(const std::string &filename) +{ + std::ifstream file(filename); + if (!file) + { + LOG_F(ERROR, "Failed to open file for reading: {}", filename); + return false; + } + + json jsonData; + try + { + file >> jsonData; + } + catch (const json::parse_error &e) + { + LOG_F(ERROR, "Failed to parse JSON: {}", e.what()); + file.close(); + return false; + } + + for (auto it = jsonData.begin(); it != jsonData.end(); ++it) + { + std::string section = it.key(); + for (auto entryIt = it.value().begin(); entryIt != it.value().end(); ++entryIt) + { + std::string key = entryIt.key(); + data[section][key] = entryIt.value(); + } + } + + file.close(); + return true; +} + +std::string IParams::toJson() const +{ + json jsonData; + for (const auto §ion : data) + { + json sectionData; + for (const auto &entry : section.second) + { + try + { + if (entry.second.type() == typeid(int)) + { + sectionData[entry.first] = std::any_cast(entry.second); + } + else if (entry.second.type() == typeid(float)) + { + sectionData[entry.first] = std::any_cast(entry.second); + } + else if (entry.second.type() == typeid(double)) + { + sectionData[entry.first] = std::any_cast(entry.second); + } + else if (entry.second.type() == typeid(std::string)) + { + sectionData[entry.first] = std::any_cast(entry.second); + } + else if (entry.second.type() == typeid(const char *)) + { + sectionData[entry.first] = std::any_cast(entry.second); + } + else if (entry.second.type() == typeid(bool)) + { + sectionData[entry.first] = std::any_cast(entry.second); + } + else + { + throw std::runtime_error("Unsupported type"); + } + } + catch (const std::bad_any_cast &e) + { + LOG_F(ERROR, "Failed to cast any_cast: {}", e.what()) + } + } + jsonData[section.first] = sectionData; + } + + return jsonData.dump(4); +} + +bool IParams::fromJson(const std::string &jsonStr) +{ + json jsonData; + try + { + jsonData = json::parse(jsonStr); + } + catch (const json::parse_error &e) + { + LOG_F(ERROR, "Failed to parse JSON: {}", e.what()); + return false; + } + + for (auto it = jsonData.begin(); it != jsonData.end(); ++it) + { + std::string section = it.key(); + for (auto entryIt = it.value().begin(); entryIt != it.value().end(); ++entryIt) + { + std::string key = entryIt.key(); + data[section][key] = entryIt.value(); + } + } + + return true; +} \ No newline at end of file diff --git a/src/atom/type/iparams.hpp b/src/atom/type/iparams.hpp new file mode 100644 index 00000000..a2c2e47b --- /dev/null +++ b/src/atom/type/iparams.hpp @@ -0,0 +1,130 @@ +/* + * iparams.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-17 + +Description: Special Parameters Library for Atom + +**************************************************/ + +#pragma once + +#include +#include +#include +#if ENABLE_FASTHASH +#include "emhash/hash_table8.hpp" +#else +#include +#endif +#include + +using json = nlohmann::json; + +class IParams +{ +public: + /** + * 设置参数 + * @tparam T 类型 + * @param section 部分名 + * @param key 键 + * @param value 值 + */ + template + void set(const std::string §ion, const std::string &key, const T &value); + + /** + * 获取参数的值 + * @tparam T 类型 + * @param section 部分名 + * @param key 键 + * @return 值,如果不存在则返回std::nullopt + */ + template + std::optional get(const std::string §ion, const std::string &key) const; + + /** + * 序列化参数到文件 + * @param filename 文件名 + * @return 是否成功 + */ + bool serialize(const std::string &filename) const; + + /** + * 从文件反序列化参数 + * @param filename 文件名 + * @return 是否成功 + */ + bool deserialize(const std::string &filename); + + /** + * 将参数序列化为 JSON 字符串 + * @return JSON 字符串 + */ + std::string toJson() const; + + /** + * 从 JSON 字符串反序列化参数 + * @param jsonStr JSON 字符串 + * @return 是否成功 + */ + bool fromJson(const std::string &jsonStr); + +private: +#if ENABLE_FASTHASH + emhash8::HashMap> data; // 存储数据的映射表 +#else + std::unordered_map> data; // 存储数据的映射表 +#endif +}; + +template +void IParams::set(const std::string §ion, const std::string &key, const T &value) +{ + data[section][key] = value; +} + +template +std::optional IParams::get(const std::string §ion, const std::string &key) const +{ + auto it = data.find(section); + if (it != data.end()) + { + auto entryIt = it->second.find(key); + if (entryIt != it->second.end()) + { + try + { + return std::any_cast(entryIt->second); + } + catch (const std::bad_any_cast &) + { + return std::nullopt; + } + } + } + return std::nullopt; +} diff --git a/src/atom/type/small_hashmap.hpp b/src/atom/type/small_hashmap.hpp new file mode 100644 index 00000000..190c962d --- /dev/null +++ b/src/atom/type/small_hashmap.hpp @@ -0,0 +1,122 @@ +/* + * small_hashmap.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-17 + +Description: A Small Hash Map Implementation + +**************************************************/ + +#include + +template +class SmallHashMap +{ +public: + // 构造函数 + SmallHashMap() + { + m_data.reserve(N); + } + + // 插入键值对 + void insert(const K &key, const V &value) + { + m_data[key] = value; + } + + // 通过键获取值 + V get(const K &key) + { + auto it = m_data.find(key); + if (it != m_data.end()) + { + return it->second; + } + else + { + return V(); + } + } + + // 删除键值对 + void erase(const K &key) + { + m_data.erase(key); + } + + // 清空哈希表 + void clear() + { + m_data.clear(); + } + + // 获取键值对个数 + size_t size() const + { + return m_data.size(); + } + + // 判断哈希表是否为空 + bool empty() const + { + return m_data.empty(); + } + +private: + std::unordered_map m_data; // 存储数据的容器 +}; + +/* + +int main() +{ + SmallHashMap hashMap; + + hashMap.insert("apple", 1); + hashMap.insert("banana", 2); + hashMap.insert("orange", 3); + hashMap.insert("grape", 4); + hashMap.insert("watermelon", 5); + + std::cout << "Value of 'banana': " << hashMap.get("banana") << std::endl; + std::cout << "Value of 'watermelon': " << hashMap.get("watermelon") << std::endl; + + hashMap.erase("banana"); + + std::cout << "Value of 'banana' after erase: " << hashMap.get("banana") << std::endl; + + std::cout << "Size: " << hashMap.size() << std::endl; + std::cout << "Empty: " << (hashMap.empty() ? "Yes" : "No") << std::endl; + + hashMap.clear(); + + std::cout << "Size after clear: " << hashMap.size() << std::endl; + std::cout << "Empty after clear: " << (hashMap.empty() ? "Yes" : "No") << std::endl; + + return 0; +} + +*/ \ No newline at end of file diff --git a/src/atom/type/small_vector.hpp b/src/atom/type/small_vector.hpp new file mode 100644 index 00000000..c5916ea2 --- /dev/null +++ b/src/atom/type/small_vector.hpp @@ -0,0 +1,213 @@ +/* + * small_vector.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-17 + +Description: A Small Vector Implementation + +**************************************************/ + +#pragma once + +#include +#include + +template +class SmallVector +{ +public: + // 构造函数 + SmallVector() : m_size(0), m_capacity(N) + { + m_data = m_inlineData; + } + + // 复制构造函数 + SmallVector(const SmallVector &other) : m_size(other.m_size), m_capacity(other.m_capacity) + { + if (other.m_size > N) + { + m_data = new T[other.m_capacity]; + std::copy(other.m_data, other.m_data + other.m_size, m_data); + } + else + { + m_data = m_inlineData; + std::copy(other.m_data, other.m_data + other.m_size, m_data); + } + } + + // 移动构造函数 + SmallVector(SmallVector &&other) noexcept : m_size(other.m_size), m_capacity(other.m_capacity), m_data(other.m_data) + { + if (other.m_size > N) + { + other.m_data = nullptr; + } + else + { + std::copy(other.m_inlineData, other.m_inlineData + other.m_size, m_inlineData); + } + } + + // 析构函数 + ~SmallVector() + { + if (m_data != m_inlineData) + { + delete[] m_data; + } + } + + // 赋值操作符重载 + SmallVector &operator=(const SmallVector &other) + { + if (this != &other) + { + m_size = other.m_size; + m_capacity = other.m_capacity; + if (other.m_size > N) + { + if (m_data != m_inlineData) + { + delete[] m_data; + } + m_data = new T[other.m_capacity]; + std::copy(other.m_data, other.m_data + other.m_size, m_data); + } + else + { + if (m_data != m_inlineData) + { + delete[] m_data; + } + m_data = m_inlineData; + std::copy(other.m_data, other.m_data + other.m_size, m_data); + } + } + return *this; + } + + // 添加元素 + void push_back(const T &value) + { + if (m_size == m_capacity) + { + if (m_capacity == N) + { + m_capacity *= 2; + m_data = new T[m_capacity]; + std::copy(m_inlineData, m_inlineData + m_size, m_data); + } + else + { + m_capacity *= 2; + T *newData = new T[m_capacity]; + std::copy(m_data, m_data + m_size, newData); + delete[] m_data; + m_data = newData; + } + } + m_data[m_size++] = value; + } + + // 删除最后一个元素 + void pop_back() + { + if (m_size > 0) + { + --m_size; + } + } + + // 清空向量 + void clear() + { + m_size = 0; + } + + // 获取元素个数 + size_t size() const + { + return m_size; + } + + // 获取容量大小 + size_t capacity() const + { + return m_capacity; + } + + // 判断向量是否为空 + bool empty() const + { + return m_size == 0; + } + + // 通过索引访问元素 + T &operator[](size_t index) + { + return m_data[index]; + } + +private: + size_t m_size; // 元素个数 + size_t m_capacity; // 容量大小 + T *m_data; // 存储数据的指针 + T m_inlineData[N]; // 内置的数据存储空间 +}; + +/* + +int main() +{ + SmallVector vec; + + vec.push_back(1); + vec.push_back(2); + vec.push_back(3); + vec.push_back(4); + vec.push_back(5); + + std::cout << "Size: " << vec.size() << std::endl; + std::cout << "Capacity: " << vec.capacity() << std::endl; + + for (size_t i = 0; i < vec.size(); ++i) + { + std::cout << vec[i] << " "; + } + std::cout << std::endl; + + vec.pop_back(); + vec.clear(); + + std::cout << "Size: " << vec.size() << std::endl; + std::cout << "Capacity: " << vec.capacity() << std::endl; + std::cout << "Empty: " << (vec.empty() ? "Yes" : "No") << std::endl; + + return 0; +} + +*/ \ No newline at end of file diff --git a/src/atom/utils/env.cpp b/src/atom/utils/env.cpp new file mode 100644 index 00000000..22b66b16 --- /dev/null +++ b/src/atom/utils/env.cpp @@ -0,0 +1,248 @@ +/* + * env.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-16 + +Description: Environment variable management + +**************************************************/ + +#include "env.hpp" + +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#include "atom/log/loguru.hpp" + +Env::Env(int argc, char **argv) +{ + std::filesystem::path exe_path; + +#ifdef _WIN32 + char buf[MAX_PATH]; + GetModuleFileName(NULL, buf, MAX_PATH); + exe_path = buf; +#else + char link_buf[1024]; + ssize_t count = readlink("/proc/self/exe", link_buf, sizeof(link_buf)); + if (count != -1) + { + link_buf[count] = '\0'; + exe_path = link_buf; + } +#endif + + m_exe = exe_path.string(); + + m_cwd = exe_path.parent_path().string() + '/'; + + m_program = argv[0]; + + if (argc > 1) + { + int i = 1; + int j; + for (j = 2; j < argc; ++j) + { + if (argv[i][0] == '-' && argv[j][0] == '-') + { + add(std::string(argv[i] + 1), ""); + i = j; + } + else if (argv[i][0] == '-' && argv[j][0] != '-') + { + add(std::string(argv[i] + 1), std::string(argv[j])); + ++j; + i = j; + } + else + { + return; + } + } + + if (i < argc) + { + if (argv[i][0] == '-') + { + add(std::string(argv[i] + 1), ""); + } + else + { + return; + } + } + } +} + +void Env::add(const std::string &key, const std::string &val) +{ + std::lock_guard lock(m_mutex); + m_args[key] = val; +} + +bool Env::has(const std::string &key) +{ + std::lock_guard lock(m_mutex); + return m_args.count(key) > 0; +} + +void Env::del(const std::string &key) +{ + std::lock_guard lock(m_mutex); + m_args.erase(key); +} + +std::string Env::get(const std::string &key, const std::string &default_value) +{ + std::lock_guard lock(m_mutex); + auto it = m_args.find(key); + return it != m_args.end() ? it->second : default_value; +} + +void Env::addHelp(const std::string &key, const std::string &desc) +{ + std::lock_guard lock(m_mutex); + m_helps.push_back(std::make_pair(key, desc)); +} + +void Env::removeHelp(const std::string &key) +{ + std::lock_guard lock(m_mutex); + m_helps.erase(std::remove_if(m_helps.begin(), m_helps.end(), + [&](const std::pair &p) + { + return p.first == key; + }), + m_helps.end()); +} + +void Env::printHelp() +{ + std::lock_guard lock(m_mutex); + LOG_F(INFO, "Usage: {} [options]", m_program); + for (const auto &i : m_helps) + { + LOG_F(INFO, " {} : {}", i.first, i.second); + } +} + +bool Env::setEnv(const std::string &key, const std::string &val) +{ +#ifdef _WIN32 + return SetEnvironmentVariableA(key.c_str(), val.c_str()) != 0; +#else + return setenv(key.c_str(), val.c_str(), 1) == 0; +#endif +} + +std::string Env::getEnv(const std::string &key, const std::string &default_value) +{ +#ifdef _WIN32 + char buf[1024]; + DWORD ret = GetEnvironmentVariableA(key.c_str(), buf, sizeof(buf)); + if (ret == 0 || ret >= sizeof(buf)) + { + return default_value; + } + return buf; +#else + const char *v = getenv(key.c_str()); + if (v == nullptr) + { + return default_value; + } + return v; +#endif +} + +std::string Env::getAbsolutePath(const std::string &path) const +{ + if (path.empty()) + { + return "/"; + } +#ifdef _WIN32 + if (path[1] == ':') + { + return path; + } +#else + if (path[0] == '/') + { + return path; + } +#endif + return m_cwd + path; +} + +std::string Env::getAbsoluteWorkPath(const std::string &path) const +{ + if (path.empty()) + { + return "/"; + } +#ifdef _WIN32 + if (path[1] == ':') + { + return path; + } +#else + if (path[0] == '/') + { + return path; + } +#endif + return path; +} + +std::string Env::getConfigPath() +{ + return getAbsolutePath(get("c", "config")); +} + +/* +int main(int argc, char **argv) +{ + johnsonli::Env env(argc, argv); + env.addHelp("h", "Print help message"); + env.addHelp("c", "Set config file"); + + if (env.has("h")) + { + env.printHelp(); + return 0; + } + + std::cout << "Config Path: " << env.getConfigPath() << std::endl; + + return 0; +} +*/ diff --git a/src/atom/utils/env.hpp b/src/atom/utils/env.hpp new file mode 100644 index 00000000..d67c1713 --- /dev/null +++ b/src/atom/utils/env.hpp @@ -0,0 +1,66 @@ +/* + * env.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-16 + +Description: Environment variable management + +**************************************************/ + +#pragma once + +#include +#include +#include +#include + +class Env +{ +public: + Env(int argc, char **argv); + + void add(const std::string &key, const std::string &val); + bool has(const std::string &key); + void del(const std::string &key); + std::string get(const std::string &key, const std::string &default_value); + void addHelp(const std::string &key, const std::string &desc); + void removeHelp(const std::string &key); + void printHelp(); + bool setEnv(const std::string &key, const std::string &val); + std::string getEnv(const std::string &key, const std::string &default_value); + std::string getAbsolutePath(const std::string &path) const; + std::string getAbsoluteWorkPath(const std::string &path) const; + std::string getConfigPath(); + +private: + std::string m_exe; + std::string m_cwd; + std::string m_program; + + std::unordered_map m_args; + std::vector> m_helps; + + mutable std::mutex m_mutex; +}; diff --git a/src/atom/utils/hash_util.cpp b/src/atom/utils/hash_util.cpp new file mode 100644 index 00000000..55008dd0 --- /dev/null +++ b/src/atom/utils/hash_util.cpp @@ -0,0 +1,672 @@ +/* + * hash_util.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-16 + +Description: Implementation of murmur3 hash and quick hash + +**************************************************/ + +#include "hash_util.hpp" + +#include +#include +#include +#include +#include +#include + +#define ROTL(x, r) ((x << r) | (x >> (32 - r))) + +static inline uint32_t fmix32(uint32_t h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + +uint32_t murmur3_hash(const void *data, const uint32_t &size, const uint32_t &seed) +{ + if (!data) + return 0; + + const char *str = (const char *)data; + uint32_t s, h = seed, + seed1 = 0xcc9e2d51, + seed2 = 0x1b873593, + *ptr = (uint32_t *)str; + + // handle begin blocks + int len = size; + int blk = len / 4; + for (int i = 0; i < blk; i++) + { + s = ptr[i]; + s *= seed1; + s = ROTL(s, 15); + s *= seed2; + + h ^= s; + h = ROTL(h, 13); + h *= 5; + h += 0xe6546b64; + } + + // handle tail + s = 0; + uint8_t *tail = (uint8_t *)(str + blk * 4); + switch (len & 3) + { + case 3: + s |= tail[2] << 16; + case 2: + s |= tail[1] << 8; + case 1: + s |= tail[0]; + + s *= seed1; + s = ROTL(s, 15); + s *= seed2; + h ^= s; + }; + + return fmix32(h ^ len); +} + +uint32_t murmur3_hash(const char *str, const uint32_t &seed) +{ + if (!str) + return 0; + + uint32_t s, h = seed, + seed1 = 0xcc9e2d51, + seed2 = 0x1b873593, + *ptr = (uint32_t *)str; + + // handle begin blocks + int len = (int)strlen(str); + int blk = len / 4; + for (int i = 0; i < blk; i++) + { + s = ptr[i]; + s *= seed1; + s = ROTL(s, 15); + s *= seed2; + + h ^= s; + h = ROTL(h, 13); + h *= 5; + h += 0xe6546b64; + } + + // handle tail + s = 0; + uint8_t *tail = (uint8_t *)(str + blk * 4); + switch (len & 3) + { + case 3: + s |= tail[2] << 16; + case 2: + s |= tail[1] << 8; + case 1: + s |= tail[0]; + + s *= seed1; + s = ROTL(s, 15); + s *= seed2; + h ^= s; + }; + + return fmix32(h ^ len); +} + +uint32_t quick_hash(const char *str) +{ + unsigned int h = 0; + for (; *str; str++) + { + h = 31 * h + *str; + } + return h; +} + +uint32_t quick_hash(const void *tmp, uint32_t size) +{ + const char *str = (const char *)tmp; + unsigned int h = 0; + for (uint32_t i = 0; i < size; ++i) + { + h = 31 * h + *str; + } + return h; +} + +uint64_t murmur3_hash64(const void *str, const uint32_t &size, const uint32_t &seed, const uint32_t &seed2) +{ + return (((uint64_t)murmur3_hash(str, size, seed)) << 32 | murmur3_hash(str, size, seed2)); +} +uint64_t murmur3_hash64(const char *str, const uint32_t &seed, const uint32_t &seed2) +{ + return (((uint64_t)murmur3_hash(str, seed)) << 32 | murmur3_hash(str, seed2)); +} + +std::string base64decode(const std::string &src) +{ + std::string result; + result.resize(src.size() * 3 / 4); + char *writeBuf = &result[0]; + + const char *ptr = src.c_str(); + const char *end = ptr + src.size(); + + while (ptr < end) + { + int i = 0; + int padding = 0; + int packed = 0; + for (; i < 4 && ptr < end; ++i, ++ptr) + { + if (*ptr == '=') + { + ++padding; + packed <<= 6; + continue; + } + + // padding with "=" only + if (padding > 0) + { + return ""; + } + + int val = 0; + if (*ptr >= 'A' && *ptr <= 'Z') + { + val = *ptr - 'A'; + } + else if (*ptr >= 'a' && *ptr <= 'z') + { + val = *ptr - 'a' + 26; + } + else if (*ptr >= '0' && *ptr <= '9') + { + val = *ptr - '0' + 52; + } + else if (*ptr == '+') + { + val = 62; + } + else if (*ptr == '/') + { + val = 63; + } + else + { + return ""; // invalid character + } + + packed = (packed << 6) | val; + } + if (i != 4) + { + return ""; + } + if (padding > 0 && ptr != end) + { + return ""; + } + if (padding > 2) + { + return ""; + } + + *writeBuf++ = (char)((packed >> 16) & 0xff); + if (padding != 2) + { + *writeBuf++ = (char)((packed >> 8) & 0xff); + } + if (padding == 0) + { + *writeBuf++ = (char)(packed & 0xff); + } + } + + result.resize(writeBuf - result.c_str()); + return result; +} + +std::string base64encode(const std::string &data) +{ + return base64encode(data.c_str(), data.size()); +} + +std::string base64encode(const void *data, size_t len) +{ + const char *base64 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string ret; + ret.reserve(len * 4 / 3 + 2); + + const unsigned char *ptr = (const unsigned char *)data; + const unsigned char *end = ptr + len; + + while (ptr < end) + { + unsigned int packed = 0; + int i = 0; + int padding = 0; + for (; i < 3 && ptr < end; ++i, ++ptr) + { + packed = (packed << 8) | *ptr; + } + if (i == 2) + { + padding = 1; + } + else if (i == 1) + { + padding = 2; + } + for (; i < 3; ++i) + { + packed <<= 8; + } + + ret.append(1, base64[packed >> 18]); + ret.append(1, base64[(packed >> 12) & 0x3f]); + if (padding != 2) + { + ret.append(1, base64[(packed >> 6) & 0x3f]); + } + if (padding == 0) + { + ret.append(1, base64[packed & 0x3f]); + } + ret.append(padding, '='); + } + + return ret; +} + +std::string md5(const std::string &data) +{ + return hexstring_from_data(md5sum(data).c_str(), MD5_DIGEST_LENGTH); +} + +std::string sha1(const std::string &data) +{ + return hexstring_from_data(sha1sum(data).c_str(), SHA_DIGEST_LENGTH); +} + +std::string md5sum(const void *data, size_t len) +{ + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, data, len); + std::string result; + result.resize(MD5_DIGEST_LENGTH); + MD5_Final((unsigned char *)&result[0], &ctx); + return result; +} + +std::string md5sum(const std::string &data) +{ + return md5sum(data.c_str(), data.size()); +} + +// std::string sha0sum(const void *data, size_t len) { +// SHA_CTX ctx; +// SHA0_Init(&ctx); +// SHA1_Update(&ctx, data, len); +// std::string result; +// result.resize(SHA_DIGEST_LENGTH); +// SHA1_Final((unsigned char*)&result[0], &ctx); +// return result; +// } + +// std::string sha0sum(const std::string & data) { +// return sha0sum(data.c_str(), data.length()); +// } + +std::string sha1sum(const void *data, size_t len) +{ + SHA_CTX ctx; + SHA1_Init(&ctx); + SHA1_Update(&ctx, data, len); + std::string result; + result.resize(SHA_DIGEST_LENGTH); + SHA1_Final((unsigned char *)&result[0], &ctx); + return result; +} + +std::string sha1sum(const std::string &data) +{ + return sha1sum(data.c_str(), data.size()); +} + +struct xorStruct +{ + xorStruct(char value) : m_value(value) {} + char m_value; + char operator()(char in) const { return in ^ m_value; } +}; + +template +std::string hmac(const std::string &text, const std::string &key) +{ + std::string keyLocal = key; + CTX ctx; + if (keyLocal.size() > B) + { + Init(&ctx); + Update(&ctx, keyLocal.c_str(), keyLocal.size()); + keyLocal.resize(L); + Final((unsigned char *)&keyLocal[0], &ctx); + } + keyLocal.append(B - keyLocal.size(), '\0'); + std::string ipad = keyLocal, opad = keyLocal; + std::transform(ipad.begin(), ipad.end(), ipad.begin(), xorStruct(0x36)); + std::transform(opad.begin(), opad.end(), opad.begin(), xorStruct(0x5c)); + Init(&ctx); + Update(&ctx, ipad.c_str(), B); + Update(&ctx, text.c_str(), text.size()); + std::string result; + result.resize(L); + Final((unsigned char *)&result[0], &ctx); + Init(&ctx); + Update(&ctx, opad.c_str(), B); + Update(&ctx, result.c_str(), L); + Final((unsigned char *)&result[0], &ctx); + return result; +} + +std::string hmac_md5(const std::string &text, const std::string &key) +{ + return hmac(text, key); +} + +std::string hmac_sha1(const std::string &text, const std::string &key) +{ + return hmac(text, key); +} + +std::string +hmac_sha256(const std::string &text, const std::string &key) +{ + return hmac(text, key); +} + +void hexstring_from_data(const void *data, size_t len, char *output) +{ + const unsigned char *buf = (const unsigned char *)data; + size_t i, j; + for (i = j = 0; i < len; ++i) + { + char c; + c = (buf[i] >> 4) & 0xf; + c = (c > 9) ? c + 'a' - 10 : c + '0'; + output[j++] = c; + c = (buf[i] & 0xf); + c = (c > 9) ? c + 'a' - 10 : c + '0'; + output[j++] = c; + } +} + +std::string +hexstring_from_data(const void *data, size_t len) +{ + if (len == 0) + { + return std::string(); + } + std::string result; + result.resize(len * 2); + hexstring_from_data(data, len, &result[0]); + return result; +} + +std::string hexstring_from_data(const std::string &data) +{ + return hexstring_from_data(data.c_str(), data.size()); +} + +void data_from_hexstring(const char *hexstring, size_t length, void *output) +{ + unsigned char *buf = (unsigned char *)output; + unsigned char byte; + if (length % 2 != 0) + { + throw std::invalid_argument("data_from_hexstring length % 2 != 0"); + } + for (size_t i = 0; i < length; ++i) + { + switch (hexstring[i]) + { + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + byte = (hexstring[i] - 'a' + 10) << 4; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + byte = (hexstring[i] - 'A' + 10) << 4; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + byte = (hexstring[i] - '0') << 4; + break; + default: + throw std::invalid_argument("data_from_hexstring invalid hexstring"); + } + ++i; + switch (hexstring[i]) + { + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + byte |= hexstring[i] - 'a' + 10; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + byte |= hexstring[i] - 'A' + 10; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + byte |= hexstring[i] - '0'; + break; + default: + throw std::invalid_argument("data_from_hexstring invalid hexstring"); + } + *buf++ = byte; + } +} + +std::string data_from_hexstring(const char *hexstring, size_t length) +{ + if (length % 2 != 0) + { + throw std::invalid_argument("data_from_hexstring length % 2 != 0"); + } + if (length == 0) + { + return std::string(); + } + std::string result; + result.resize(length / 2); + data_from_hexstring(hexstring, length, &result[0]); + return result; +} + +std::string data_from_hexstring(const std::string &hexstring) +{ + return data_from_hexstring(hexstring.c_str(), hexstring.size()); +} + +std::string replace(const std::string &str1, char find, char replaceWith) +{ + auto str = str1; + size_t index = str.find(find); + while (index != std::string::npos) + { + str[index] = replaceWith; + index = str.find(find, index + 1); + } + return str; +} + +std::string replace(const std::string &str1, char find, const std::string &replaceWith) +{ + auto str = str1; + size_t index = str.find(find); + while (index != std::string::npos) + { + str = str.substr(0, index) + replaceWith + str.substr(index + 1); + index = str.find(find, index + replaceWith.size()); + } + return str; +} + +std::string replace(const std::string &str1, const std::string &find, const std::string &replaceWith) +{ + auto str = str1; + size_t index = str.find(find); + while (index != std::string::npos) + { + str = str.substr(0, index) + replaceWith + str.substr(index + find.size()); + index = str.find(find, index + replaceWith.size()); + } + return str; +} + +std::vector split(const std::string &str, char delim, size_t max) +{ + std::vector result; + if (str.empty()) + { + return result; + } + + size_t last = 0; + size_t pos = str.find(delim); + while (pos != std::string::npos) + { + result.push_back(str.substr(last, pos - last)); + last = pos + 1; + if (--max == 1) + break; + pos = str.find(delim, last); + } + result.push_back(str.substr(last)); + return result; +} + +std::vector split(const std::string &str, const char *delims, size_t max) +{ + std::vector result; + if (str.empty()) + { + return result; + } + + size_t last = 0; + size_t pos = str.find_first_of(delims); + while (pos != std::string::npos) + { + result.push_back(str.substr(last, pos - last)); + last = pos + 1; + if (--max == 1) + break; + pos = str.find_first_of(delims, last); + } + result.push_back(str.substr(last)); + return result; +} + +std::string random_string(size_t len, const std::string &chars) +{ + if (len == 0 || chars.empty()) + { + return ""; + } + std::string rt; + rt.resize(len); + int count = chars.size(); + for (size_t i = 0; i < len; ++i) + { + rt[i] = chars[rand() % count]; + } + return rt; +} \ No newline at end of file diff --git a/src/atom/utils/hash_util.hpp b/src/atom/utils/hash_util.hpp new file mode 100644 index 00000000..702edd34 --- /dev/null +++ b/src/atom/utils/hash_util.hpp @@ -0,0 +1,292 @@ +/* + * hash_util.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-16 + +Description: Implementation of murmur3 hash and quick hash + +**************************************************/ + +#pragma once + +#include +#include +#include + +/** + * @brief Calculates the MurmurHash3 hash value for a given string. + * + * @param str The input string. + * @param seed The seed value (optional, default is 1060627423). + * @return uint32_t The calculated hash value. + */ +uint32_t murmur3_hash(const char* str, const uint32_t& seed = 1060627423); + +/** + * @brief Calculates the 64-bit MurmurHash3 hash value for a given string. + * + * @param str The input string. + * @param seed The first seed value (optional, default is 1060627423). + * @param seed2 The second seed value (optional, default is 1050126127). + * @return uint64_t The calculated hash value. + */ +uint64_t murmur3_hash64(const char* str, const uint32_t& seed = 1060627423, const uint32_t& seed2 = 1050126127); + +/** + * @brief Calculates the MurmurHash3 hash value for a given data buffer. + * + * @param str The input data buffer. + * @param size The size of the data buffer. + * @param seed The seed value (optional, default is 1060627423). + * @return uint32_t The calculated hash value. + */ +uint32_t murmur3_hash(const void* str, const uint32_t& size, const uint32_t& seed = 1060627423); + +/** + * @brief Calculates the 64-bit MurmurHash3 hash value for a given data buffer. + * + * @param str The input data buffer. + * @param size The size of the data buffer. + * @param seed The first seed value (optional, default is 1060627423). + * @param seed2 The second seed value (optional, default is 1050126127). + * @return uint64_t The calculated hash value. + */ +uint64_t murmur3_hash64(const void* str, const uint32_t& size, const uint32_t& seed = 1060627423, const uint32_t& seed2 = 1050126127); + +/** + * @brief Calculates the quick hash value for a given string. + * + * @param str The input string. + * @return uint32_t The calculated hash value. + */ +uint32_t quick_hash(const char* str); + +/** + * @brief Calculates the quick hash value for a given data buffer. + * + * @param str The input data buffer. + * @param size The size of the data buffer. + * @return uint32_t The calculated hash value. + */ +uint32_t quick_hash(const void* str, uint32_t size); + +/** + * @brief Decodes a Base64 encoded string. + * + * @param src The Base64 encoded string. + * @return std::string The decoded string. + */ +std::string base64decode(const std::string& src); + +/** + * @brief Encodes a string as Base64. + * + * @param data The input string. + * @return std::string The Base64 encoded string. + */ +std::string base64encode(const std::string& data); + +/** + * @brief Encodes a data buffer as Base64. + * + * @param data The input data buffer. + * @param len The length of the data buffer. + * @return std::string The Base64 encoded string. + */ +std::string base64encode(const void* data, size_t len); + +/** + * @brief Calculates the MD5 hash value for a given string. + * + * @param data The input string. + * @return std::string The MD5 hash value in hexadecimal format. + */ +std::string md5(const std::string& data); + +/** + * @brief Calculates the SHA1 hash value for a given string. + * + * @param data The input string. + * @return std::string The SHA1 hash value in hexadecimal format. + */ +std::string sha1(const std::string& data); + +/** + * @brief Calculates the MD5 hash value for a given data buffer. + * + * @param data The input data buffer. + * @param len The length of the data buffer. + * @return std::string The MD5 hash value as raw binary data. + */ +std::string md5sum(const void* data, size_t len); + +/** + * @brief Calculates the SHA1 hash value for a given data buffer. + * + * @param data The input data buffer. + * @param len The length of the data buffer. + * @return std::string The SHA1 hash value as raw binary data. + */ +std::string sha1sum(const void* data, size_t len); + +/** + * @brief Calculates the HMAC-MD5 hash value for a given text and key. + * + * @param text The input text. + * @param key The HMAC key. + * @return std::string The HMAC-MD5 hash value in hexadecimal format. + */ +std::string hmac_md5(const std::string& text, const std::string& key); + +/** + * @brief Calculates the HMAC-SHA1 hash value for a given text and key. + * + * @param text The input text. + * @param key The HMAC key. + * @return std::string The HMAC-SHA1 hash value in hexadecimal format. + */ +std::string hmac_sha1(const std::string& text, const std::string& key); + +/** + * @brief Calculates the HMAC-SHA256 hash value for a given text and key. + * + * @param text The input text. + * @param key The HMAC key. + * @return std::string The HMAC-SHA256 hash value in hexadecimal format. + */ +std::string hmac_sha256(const std::string& text, const std::string& key); + +/** + * @brief Converts binary data to a hexadecimal string representation. + * + * @param data The input data buffer. + * @param len The length of the data buffer. + * @param output The output buffer to store the hexadecimal string (length must be len * 2). + */ +void hexstring_from_data(const void* data, size_t len, char* output); + +/** + * @brief Converts binary data to a hexadecimal string representation. + * + * @param data The input data buffer. + * @param len The length of the data buffer. + * @return std::string The hexadecimal string representation. + */ +std::string hexstring_from_data(const void* data, size_t len); + +/** + * @brief Converts a string to a hexadecimal string representation. + * + * @param data The input string. + * @return std::string The hexadecimal string representation. + */ +std::string hexstring_from_data(const std::string& data); + +/** + * @brief Converts a hexadecimal string representation to binary data. + * + * @param hexstring The input hexadecimal string. + * @param length The length of the hexadecimal string. + * @param output The output buffer to store the binary data (length must be length / 2). + */ +void data_from_hexstring(const char* hexstring, size_t length, void* output); + +/** + * @brief Converts a hexadecimal string representation to binary data. + * + * @param hexstring The input hexadecimal string. + * @param length The length of the hexadecimal string. + * @return std::string The binary data. + * @throw std::invalid_argument If the input hexstring is not a valid hexadecimal string. + */ +std::string data_from_hexstring(const char* hexstring, size_t length); + +/** + * @brief Converts a hexadecimal string representation to binary data. + * + * @param data The input hexadecimal string. + * @return std::string The binary data. + * @throw std::invalid_argument If the input hexstring is not a valid hexadecimal string. + */ +std::string data_from_hexstring(const std::string& data); + +/** + * @brief Replaces all occurrences of a character in a string with another character. + * + * @param str The input string. + * @param find The character to find. + * @param replaceWith The character to replace with. + * @return std::string The modified string. + */ +std::string replace(const std::string& str, char find, char replaceWith); + +/** + * @brief Replaces all occurrences of a character in a string with another string. + * + * @param str The input string. + * @param find The character to find. + * @param replaceWith The string to replace with. + * @return std::string The modified string. + */ +std::string replace(const std::string& str, char find, const std::string& replaceWith); + +/** + * @brief Replaces all occurrences of a substring in a string with another substring. + * + * @param str The input string. + * @param find The substring to find. + * @param replaceWith The substring to replace with. + * @return std::string The modified string. + */ +std::string replace(const std::string& str, const std::string& find, const std::string& replaceWith); + +/** + * @brief Splits a string into a vector of substrings using a delimiter character. + * + * @param str The input string. + * @param delim The delimiter character. + * @param max The maximum number of splits (optional). + * @return std::vector The vector of substrings. + */ +std::vector split(const std::string& str, char delim, size_t max = ~0); + +/** + * @brief Splits a string into a vector of substrings using multiple delimiter characters. + * + * @param str The input string. + * @param delims The delimiter characters. + * @param max The maximum number of splits (optional). + * @return std::vector The vector of substrings. + */ +std::vector split(const std::string& str, const char* delims, size_t max = ~0); + +/** + * @brief Generates a random string of a specified length. + * + * @param len The length of the random string. + * @param chars The characters to choose from (optional, default is alphanumeric). + * @return std::string The random string. + */ +std::string random_string(size_t len, const std::string& chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); diff --git a/src/atom/web/address.cpp b/src/atom/web/address.cpp new file mode 100644 index 00000000..acc93db9 --- /dev/null +++ b/src/atom/web/address.cpp @@ -0,0 +1,445 @@ +#include "address.hpp" + +Address::ptr Address::Create(const std::string &address, uint16_t port) +{ + addrinfo hints, *result = nullptr; + memset(&hints, 0, sizeof(addrinfo)); + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; + + char service[32]; + snprintf(service, sizeof(service), "%u", port); + + int ret = getaddrinfo(address.c_str(), service, &hints, &result); + if (ret != 0) + { + return nullptr; + } + + Address::ptr addr; + for (addrinfo *rp = result; rp != nullptr; rp = rp->ai_next) + { + addr = Create(rp->ai_addr, rp->ai_addrlen); + if (addr != nullptr) + { + break; + } + } + + freeaddrinfo(result); + return addr; +} + +Address::ptr Address::Create(const sockaddr *addr, socklen_t addrlen) +{ + if (addr == nullptr) + { + return nullptr; + } + if (addr->sa_family == AF_INET) + { + return std::make_shared(*(const sockaddr_in *)addr); + } + else if (addr->sa_family == AF_INET6) + { + return std::make_shared(*(const sockaddr_in6 *)addr); + } + else + { + return nullptr; + } +} + +bool Address::Lookup(std::vector &result, const std::string &host, + int family, int type, int protocol) +{ + struct addrinfo hints, *res; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = type; + hints.ai_protocol = protocol; + if (getaddrinfo(host.c_str(), nullptr, &hints, &res) != 0) + { + return false; + } + + char addrbuf[INET6_ADDRSTRLEN] = {0}; + for (struct addrinfo *p = res; p != nullptr; p = p->ai_next) + { + result.push_back(Create(p->ai_addr, p->ai_addrlen)); + } + freeaddrinfo(res); + return true; +} + +Address::ptr Address::LookupAny(const std::string &host, + int family, int type, int protocol) +{ + std::vector result; + if (Lookup(result, host, family, type, protocol)) + { + if (!result.empty()) + { + return result.front(); + } + } + return nullptr; +} + +std::shared_ptr Address::LookupAnyIPAddress(const std::string &host, + int family, int type, int protocol) +{ + auto addr = LookupAny(host, family, type, protocol); + return std::dynamic_pointer_cast(addr); +} + +#ifdef _WIN32 + +bool Address::GetInterfaceAddresses(std::multimap> &result, + int family) +{ + PIP_ADAPTER_ADDRESSES pAddresses = nullptr; + ULONG outBufLen = 0; + ULONG flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST; + + DWORD ret = GetAdaptersAddresses(family, flags, nullptr, nullptr, &outBufLen); + if (ret != ERROR_BUFFER_OVERFLOW) + { + return false; + } + pAddresses = (PIP_ADAPTER_ADDRESSES)malloc(outBufLen); + ret = GetAdaptersAddresses(family, flags, nullptr, pAddresses, &outBufLen); + if (ret != ERROR_SUCCESS) + { + free(pAddresses); + return false; + } + + for (PIP_ADAPTER_ADDRESSES adapter = pAddresses; adapter != nullptr; adapter = adapter->Next) + { + for (IP_ADAPTER_UNICAST_ADDRESS *unicastAddr = adapter->FirstUnicastAddress; + unicastAddr != nullptr; + unicastAddr = unicastAddr->Next) + { + Address::ptr addr; + uint32_t prefix_len = 0; + if (family == AF_INET && unicastAddr->Address.lpSockaddr->sa_family == AF_INET) + { + addr = Create(unicastAddr->Address.lpSockaddr, sizeof(sockaddr_in)); + sockaddr_in *sai = (sockaddr_in *)unicastAddr->Address.lpSockaddr; + prefix_len = CountBytes(sai->sin_addr.s_addr); + } + else if (family == AF_INET6 && unicastAddr->Address.lpSockaddr->sa_family == AF_INET6) + { + addr = Create(unicastAddr->Address.lpSockaddr, sizeof(sockaddr_in6)); + sockaddr_in6 *sai6 = (sockaddr_in6 *)unicastAddr->Address.lpSockaddr; + prefix_len = CountBytes(sai6->sin6_addr.u.Byte); + } + if (addr) + { + result.insert(std::make_pair(adapter->AdapterName, + std::make_pair(addr, prefix_len))); + } + } + } + + free(pAddresses); + return !result.empty(); +} + +#else + +bool Address::GetInterfaceAddresses(std::multimap> &result, + int family) +{ + struct ifaddrs *res = nullptr, *ifa = nullptr; + if (getifaddrs(&res) == -1) + { + return false; + } + + for (ifa = res; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == nullptr) + { + continue; + } + Address::ptr addr; + uint32_t prefix_len = 0; + if (family == AF_INET && ifa->ifa_addr->sa_family == AF_INET) + { + addr = Create(ifa->ifa_addr, sizeof(sockaddr_in)); + sockaddr_in *sai = (sockaddr_in *)ifa->ifa_addr; + prefix_len = CountBytes(sai->sin_addr.s_addr); + } + else if (family == AF_INET6 && ifa->ifa_addr->sa_family == AF_INET6) + { + addr = Create(ifa->ifa_addr, sizeof(sockaddr_in6), false); + sockaddr_in6 *sai6 = (sockaddr_in6 *)ifa->ifa_addr; + prefix_len = CountBytes(sai6->sin6_addr.__in6_u.__u6_addr8); + } + else + { + continue; + } + if (addr) + { + result.insert(std::make_pair(ifa->ifa_name, + std::make_pair(addr, prefix_len))); + } + } + + freeifaddrs(res); + return !result.empty(); +} + +#endif + +bool Address::GetInterfaceAddresses(std::vector> &result, + const std::string &iface, int family) +{ + if (iface.empty() || iface == "*") + { + if (family == AF_INET || family == AF_UNSPEC) + { + result.push_back(std::make_pair(Create("0.0.0.0", sizeof(sockaddr_in)), 0)); + } + if (family == AF_INET6 || family == AF_UNSPEC) + { + result.push_back(std::make_pair(Create("::0", sizeof(sockaddr_in)), 0)); + } + return true; + } + + std::multimap> addrs; + if (!GetInterfaceAddresses(addrs, family)) + { + return false; + } + + auto its = addrs.equal_range(iface); + for (; its.first != its.second; ++its.first) + { + result.push_back(its.first->second); + } + return !result.empty(); +} + +IPv4Address::IPv4Address(const sockaddr_in &address) +{ + memset(&m_addr, 0, sizeof(m_addr)); + m_addr.sin_family = AF_INET; + m_addr.sin_port = address.sin_port; + m_addr.sin_addr.s_addr = address.sin_addr.s_addr; +} + +IPv4Address::IPv4Address(uint32_t address, uint16_t port) +{ + memset(&m_addr, 0, sizeof(m_addr)); + m_addr.sin_family = AF_INET; + m_addr.sin_port = htons(port); + m_addr.sin_addr.s_addr = htonl(address); +} + +IPv4Address::ptr IPv4Address::Create(const char *ip, uint16_t port) +{ + IPv4Address::ptr ret(new IPv4Address); + ret->m_addr.sin_port = htons(port); + if (inet_pton(AF_INET, ip, &ret->m_addr.sin_addr) <= 0) + { + return nullptr; + } + return ret; +} + +const sockaddr *IPv4Address::getAddr() const +{ + return (sockaddr *)&m_addr; +} + +sockaddr *IPv4Address::getAddr() +{ + return (sockaddr *)&m_addr; +} + +socklen_t IPv4Address::getAddrLen() const +{ + return sizeof(m_addr); +} + +std::ostream &IPv4Address::insert(std::ostream &os) const +{ + uint32_t addr = ntohl(m_addr.sin_addr.s_addr); + os << ((addr >> 24) & 0xff) << "." + << ((addr >> 16) & 0xff) << "." + << ((addr >> 8) & 0xff) << "." + << (addr & 0xff); + return os; +} + +IPAddress::ptr IPv4Address::broadcastAddress(uint32_t prefix_len) +{ + if (prefix_len > 32) + { + return nullptr; + } + sockaddr_in baddr(m_addr); + baddr.sin_addr.s_addr |= htonl((1 << prefix_len) - 1); + return std::make_shared(baddr); +} + +IPAddress::ptr IPv4Address::networdAddress(uint32_t prefix_len) +{ + if (prefix_len > 32) + { + return nullptr; + } + sockaddr_in naddr(m_addr); + naddr.sin_addr.s_addr &= htonl(((1 << prefix_len) - 1) << (32 - prefix_len)); + return std::make_shared(naddr); +} + +IPAddress::ptr IPv4Address::subnetMask(uint32_t prefix_len) +{ + sockaddr_in subnet; + memset(&subnet, 0, sizeof(subnet)); + subnet.sin_family = AF_INET; + subnet.sin_addr.s_addr = htonl(((1 << prefix_len) - 1) << (32 - prefix_len)); + return std::make_shared(subnet); +} + +uint16_t IPv4Address::getPort() const +{ + return ntohs(m_addr.sin_port); +} + +void IPv4Address::setPort(uint16_t v) +{ + m_addr.sin_port = htons(v); +} + +IPv6Address::IPv6Address() +{ + memset(&m_addr, 0, sizeof(m_addr)); + m_addr.sin6_family = AF_INET6; +} + +IPv6Address::IPv6Address(const sockaddr_in6 &address) +{ + memcpy(&m_addr, &address, sizeof(m_addr)); +} + +IPv6Address::IPv6Address(const uint8_t address[16], uint16_t port) +{ + memset(&m_addr, 0, sizeof(m_addr)); + m_addr.sin6_family = AF_INET6; + memcpy(&m_addr.sin6_addr.s6_addr, address, 16); + m_addr.sin6_port = htons(port); +} + +IPv6Address::ptr IPv6Address::Create(const char *address, uint16_t port) +{ + struct in6_addr addr; + if (inet_pton(AF_INET6, address, &addr) <= 0) + { + return nullptr; + } + return std::make_shared(addr.s6_addr, port); +} + +const sockaddr *IPv6Address::getAddr() const +{ + return reinterpret_cast(&m_addr); +} + +sockaddr *IPv6Address::getAddr() +{ + return reinterpret_cast(&m_addr); +} + +socklen_t IPv6Address::getAddrLen() const +{ + return static_cast(sizeof(m_addr)); +} + +std::ostream &IPv6Address::insert(std::ostream &os) const +{ + char buf[INET6_ADDRSTRLEN] = {0}; + inet_ntop(AF_INET6, &(m_addr.sin6_addr), buf, sizeof(buf)); + os << "[" << buf << "]:" << ntohs(m_addr.sin6_port); + return os; +} + +IPAddress::ptr IPv6Address::broadcastAddress(uint32_t prefix_len) +{ + if (prefix_len > 128) + { + return nullptr; + } + sockaddr_in6 baddr(m_addr); + uint32_t mask = prefix_len == 0 ? 0 : (~0ull << (128 - prefix_len)); + for (int i = 0; i < 16; ++i) + { + baddr.sin6_addr.s6_addr[i] |= ((mask >> (120 - i * 8)) & 0xff); + } + return std::make_shared(baddr); +} + +IPAddress::ptr IPv6Address::networdAddress(uint32_t prefix_len) +{ + if (prefix_len > 128) + { + return nullptr; + } + sockaddr_in6 addr(m_addr); + uint32_t mask = prefix_len == 0 ? 0 : (~0ull << (128 - prefix_len)); + for (int i = 0; i < 16; ++i) + { + addr.sin6_addr.s6_addr[i] &= ((mask >> (120 - i * 8)) & 0xff); + } + return std::make_shared(addr); +} + +IPAddress::ptr IPv6Address::subnetMask(uint32_t prefix_len) +{ + sockaddr_in6 subnet; + memset(&subnet, 0, sizeof(subnet)); + subnet.sin6_family = AF_INET6; + uint32_t mask = prefix_len == 0 ? 0 : (~0ull << (128 - prefix_len)); + for (int i = 0; i < 16; ++i) + { + subnet.sin6_addr.s6_addr[i] = (mask >> (120 - i * 8)) & 0xff; + } + return std::make_shared(subnet); +} + +uint16_t IPv6Address::getPort() const +{ + return ntohs(m_addr.sin6_port); +} + +void IPv6Address::setPort(uint16_t v) +{ + m_addr.sin6_port = htons(v); +} + +/* +int main() +{ + // 示例代码 + std::vector addrs; + if (Address::Lookup(addrs, "www.baidu.com")) + { + for (auto &addr : addrs) + { + std::cout << addr->toString() << std::endl; + } + } + + return 0; +} +*/ diff --git a/src/atom/web/address.hpp b/src/atom/web/address.hpp new file mode 100644 index 00000000..7e2d22c4 --- /dev/null +++ b/src/atom/web/address.hpp @@ -0,0 +1,171 @@ +#pragma once + +#ifdef _WIN32 +#include +#include +#include +#include +#else +#include +#endif + +#include +#include +#include +#include +#include + +template +static T CreateMask(uint32_t bits) +{ + return (1 << (sizeof(T) * 8 - bits)) - 1; +} + +template +static uint32_t CountBytes(T value) +{ + uint32_t result = 0; + for (; value; ++result) + { + value &= value - 1; + } + return result; +} + +class Address +{ +public: + typedef std::shared_ptr
        ptr; + + virtual ~Address() {} + + static Address::ptr Create(const sockaddr *addr, socklen_t addrlen); + + static Address::ptr Create(const std::string &address, uint16_t port); + + static bool Lookup(std::vector &result, const std::string &host, + int family = AF_INET, int type = 0, int protocol = 0); + + static Address::ptr LookupAny(const std::string &host, + int family = AF_INET, int type = SOCK_STREAM, int protocol = 0); + + static std::shared_ptr LookupAnyIPAddress(const std::string &host, + int family = AF_INET, int type = SOCK_STREAM, int protocol = 0); + + static bool GetInterfaceAddresses(std::multimap> &result, + int family = AF_INET); + + static bool GetInterfaceAddresses(std::vector> &result, + const std::string &iface, int family = AF_INET); + + virtual const sockaddr *getAddr() const = 0; + + virtual sockaddr *getAddr() = 0; + + virtual socklen_t getAddrLen() const = 0; + + virtual std::ostream &insert(std::ostream &os) const = 0; + + int getFamily() const; + + std::string toString() const; + + bool operator<(const Address &rhs) const; + + bool operator==(const Address &rhs) const; + + bool operator!=(const Address &rhs) const; +}; + +class IPAddress : public Address +{ +public: + typedef std::shared_ptr ptr; + + virtual ~IPAddress() {} + + static IPAddress::ptr Create(const char *address, uint16_t port = 0); + + virtual IPAddress::ptr broadcastAddress(uint32_t prefix_len) = 0; + + virtual IPAddress::ptr networdAddress(uint32_t prefix_len) = 0; + + virtual IPAddress::ptr subnetMask(uint32_t prefix_len) = 0; + + virtual uint16_t getPort() const = 0; + + virtual void setPort(uint16_t v) = 0; +}; + +#ifdef _WIN32 +#pragma comment(lib, "ws2_32.lib") +#endif + +class IPv4Address : public IPAddress +{ +public: + typedef std::shared_ptr ptr; + + IPv4Address(const sockaddr_in &address); + + IPv4Address(uint32_t address = INADDR_ANY, uint16_t port = 0); + + static IPv4Address::ptr Create(const char *ip, uint16_t port); + + const sockaddr *getAddr() const override; + + sockaddr *getAddr() override; + + socklen_t getAddrLen() const override; + + std::ostream &insert(std::ostream &os) const override; + + IPAddress::ptr broadcastAddress(uint32_t prefix_len) override; + + IPAddress::ptr networdAddress(uint32_t prefix_len) override; + + IPAddress::ptr subnetMask(uint32_t prefix_len) override; + + uint16_t getPort() const override; + + void setPort(uint16_t v) override; + +private: + sockaddr_in m_addr; +}; + +class IPv6Address : public IPAddress +{ +public: + typedef std::shared_ptr ptr; + + static IPv6Address::ptr Create(const char *address, uint16_t port = 0); + + IPv6Address(); + + IPv6Address(const sockaddr_in6 &address); + + IPv6Address(const uint8_t address[16], uint16_t port = 0); + + const sockaddr *getAddr() const override; + + sockaddr *getAddr() override; + + socklen_t getAddrLen() const override; + + std::ostream &insert(std::ostream &os) const override; + + IPAddress::ptr broadcastAddress(uint32_t prefix_len) override; + + IPAddress::ptr networdAddress(uint32_t prefix_len) override; + + IPAddress::ptr subnetMask(uint32_t prefix_len) override; + + uint16_t getPort() const override; + + void setPort(uint16_t v) override; + +private: + sockaddr_in6 m_addr; +}; \ No newline at end of file diff --git a/src/device/device_manager.cpp b/src/device/device_manager.cpp index 7b858125..c2df5a31 100644 --- a/src/device/device_manager.cpp +++ b/src/device/device_manager.cpp @@ -136,6 +136,11 @@ namespace Lithium return std::make_shared(messageBus, configManager); } + std::unique_ptr DeviceManager::createUnique(std::shared_ptr messageBus, std::shared_ptr configManager) + { + return std::make_unique(messageBus, configManager); + } + std::vector DeviceManager::getDeviceList(DeviceType type) { std::vector deviceList; diff --git a/src/device/device_manager.hpp b/src/device/device_manager.hpp index 81fc21ed..d586178b 100644 --- a/src/device/device_manager.hpp +++ b/src/device/device_manager.hpp @@ -89,6 +89,10 @@ namespace Lithium */ ~DeviceManager(); + // ------------------------------------------------------------------- + // Common methods + // ------------------------------------------------------------------- + /** * @brief 创建一个共享的设备管理器对象。 * @param messageBus 消息总线对象的共享指针。 @@ -97,6 +101,18 @@ namespace Lithium */ static std::shared_ptr createShared(std::shared_ptr messageBus, std::shared_ptr configManager); + static std::unique_ptr createUnique(std::shared_ptr messageBus, std::shared_ptr configManager); + + // ------------------------------------------------------------------- + // Message methods + // ------------------------------------------------------------------- + + std::function connectToMessageBus(); + + // ------------------------------------------------------------------- + // Device methods + // ------------------------------------------------------------------- + /** * @brief 获取指定类型设备的设备列表。 * @param type 设备类型枚举值。 From 77d50d1e1396e32eec1f46201398e992c18c089c Mon Sep 17 00:00:00 2001 From: Max Qian Date: Tue, 19 Dec 2023 12:12:41 +0800 Subject: [PATCH 9/9] update --- src/LithiumApp.cpp | 43 +++- src/LithiumApp.hpp | 94 +++++++- src/atom/CMakeLists.txt | 16 ++ src/atom/error/error_stack.cpp | 53 ++++- src/atom/error/error_stack.hpp | 20 +- src/atom/server/message.cpp | 371 +++++++++++++++----------------- src/atom/server/message.hpp | 148 +++++++++---- src/atom/server/message_bus.hpp | 18 ++ src/atom/thread/daemon.cpp | 0 src/atom/type/iparams.cpp | 31 ++- src/atom/utils/hash_util.cpp | 183 +--------------- src/atom/utils/hash_util.hpp | 86 -------- src/atom/utils/string.cpp | 92 ++++++-- src/atom/utils/string.hpp | 7 +- src/atom/utils/time.cpp | 7 + src/atom/utils/time.hpp | 8 + src/atom/web/address.hpp | 11 +- src/device/device_manager.cpp | 42 ++++ src/device/device_manager.hpp | 3 +- src/task/task_stack.hpp | 12 +- 20 files changed, 693 insertions(+), 552 deletions(-) delete mode 100644 src/atom/thread/daemon.cpp diff --git a/src/LithiumApp.cpp b/src/LithiumApp.cpp index a1d2e260..49a1b633 100644 --- a/src/LithiumApp.cpp +++ b/src/LithiumApp.cpp @@ -30,6 +30,7 @@ Description: Lithium App Enter **************************************************/ #include "LithiumApp.hpp" + #include "config.h" #include "atom/thread/thread.hpp" @@ -40,9 +41,11 @@ Description: Lithium App Enter #include "task/task_generator.hpp" #include "task/task_stack.hpp" #include "core/property/iproperty.hpp" +#include "atom/server/message.hpp" #include "plugin/plugin_loader.hpp" #include "script/script_manager.hpp" #include "atom/plugin/module_loader.hpp" +#include "atom/error/error_stack.hpp" #include "atom/server/global_ptr.hpp" @@ -60,6 +63,7 @@ namespace Lithium { try { + // Specialized Managers and Threads m_ConfigManager = GetPtr("ConfigManager"); m_DeviceManager = GetPtr("DeviceManager"); m_PluginManager = GetPtr("PluginManager"); @@ -72,12 +76,17 @@ namespace Lithium m_MessageBus = GetPtr("MessageBus"); m_ModuleLoader = GetPtr("ModuleLoader"); + // Specialized Message Processing Threads for Device and Device Manager m_MessageBus->StartProcessingThread(); m_MessageBus->StartProcessingThread(); m_MessageBus->StartProcessingThread(); m_MessageBus->StartProcessingThread(); - m_MessageBus->StartProcessingThread(); - m_MessageBus->StartProcessingThread(); + + // Common Message Processing Threads + // Max : Maybe we only need one thread for Message, and dynamically cast message + // to the right type to process. + // All of the messages are based on the Message class. + m_MessageBus->StartProcessingThread(); } catch (const std::exception &e) { @@ -88,6 +97,7 @@ namespace Lithium LithiumApp::~LithiumApp() { + m_MessageBus->UnsubscribeAll(); m_MessageBus->StopAllProcessingThreads(); } @@ -96,6 +106,11 @@ namespace Lithium return std::make_shared(); } + std::unique_ptr LithiumApp::createUnique() + { + return std::make_unique(); + } + void InitLithiumApp() { AddPtr("ConfigManager", ConfigManager::createShared()); @@ -109,6 +124,7 @@ namespace Lithium AddPtr("TaskStack", std::make_shared()); AddPtr("ScriptManager", ScriptManager::createShared(GetPtr("MessageBus"))); AddPtr("DeviceManager", DeviceManager::createShared(GetPtr("MessageBus"), GetPtr("ConfigManager"))); + AddPtr("ErrorStack", std::make_shared()); } // ---------------------------------------------------------------- @@ -127,6 +143,20 @@ namespace Lithium m_ConfigManager->setValue(key_path, value); } + ReturnMessage LithiumApp::GetConfigW(const std::shared_ptr ¶ms) + { + if(!params->get("params","key_path").has_value()) + { + LOG_F(ERROR, _("Get config value failed: Missing key_path")); + + } + } + + ReturnMessage LithiumApp::SetConfigW(const std::shared_ptr ¶ms) + { + + } + // ----------------------------------------------------------------- // Device // ----------------------------------------------------------------- @@ -535,4 +565,13 @@ namespace Lithium { return m_ModuleLoader->GetModuleConfig(name); } + + // ----------------------------------------------------------------- + // Message methods + // ----------------------------------------------------------------- + + ReturnMessage LithiumApp::returnMessage(const std::string &message) + { + + } } \ No newline at end of file diff --git a/src/LithiumApp.hpp b/src/LithiumApp.hpp index 56a4cfc0..ff144c1f 100644 --- a/src/LithiumApp.hpp +++ b/src/LithiumApp.hpp @@ -38,6 +38,9 @@ Description: Lithium App Enter #include "atom/server/message_bus.hpp" #include "device/device_manager.hpp" #include "atom/system/process.hpp" +#include "atom/server/commander.hpp" +#include "atom/type/iparams.hpp" +#include "atom/server/message.hpp" #include "atom/type/json.hpp" using json = nlohmann::json; @@ -79,19 +82,40 @@ namespace Lithium class TaskStack; } + class ErrorStack; + class LithiumApp { public: LithiumApp(); ~LithiumApp(); + // ------------------------------------------------------------------- + // Common methods + // ------------------------------------------------------------------- + static std::shared_ptr createShared(); - public: + static std::unique_ptr createUnique(); + + // ------------------------------------------------------------------- + // Config methods + // ------------------------------------------------------------------- + json GetConfig(const std::string &key_path) const; void SetConfig(const std::string &key_path, const json &value); - public: + // ------------------------------------------------------------------- + // Wrappered Config methods + // ------------------------------------------------------------------- + + ReturnMessage GetConfigW(const std::shared_ptr ¶ms); + ReturnMessage SetConfigW(const std::shared_ptr ¶ms); + + // ------------------------------------------------------------------- + // Device methods + // ------------------------------------------------------------------- + std::vector getDeviceList(DeviceType type); bool addDevice(DeviceType type, const std::string &name, const std::string &lib_name = ""); bool addDeviceLibrary(const std::string &lib_path, const std::string &lib_name); @@ -106,8 +130,10 @@ namespace Lithium bool getProperty(const std::string &name, const std::string &property_name); bool setProperty(const std::string &name, const std::string &property_name, const std::string &property_value); - public: - public: + // ------------------------------------------------------------------- + // Process methods + // ------------------------------------------------------------------- + bool createProcess(const std::string &command, const std::string &identifier); bool runScript(const std::string &script, const std::string &identifier); bool terminateProcess(pid_t pid, int signal = 15); @@ -115,7 +141,10 @@ namespace Lithium std::vector getRunningProcesses(); std::vector getProcessOutput(const std::string &identifier); - public: + // ------------------------------------------------------------------- + // Task methods + // ------------------------------------------------------------------- + bool addTask(const std::shared_ptr &task); bool insertTask(const std::shared_ptr &task, int position); bool executeAllTasks(); @@ -131,7 +160,10 @@ namespace Lithium bool checkTaskExecutable(const std::string &name); - public: + // ------------------------------------------------------------------- + // Module methods + // ------------------------------------------------------------------- + bool loadModule(const std::string &path, const std::string &name); bool unloadModule(const std::string &name); bool reloadModule(const std::string &name); @@ -142,8 +174,26 @@ namespace Lithium bool getModuleStatus(const std::string &name); json getModuleConfig(const std::string &name); std::vector getModuleList(); + + // ------------------------------------------------------------------- + // Wrappered Module methods + // ------------------------------------------------------------------- + + ReturnMessage loadModuleW(const std::shared_ptr ¶ms); + ReturnMessage unloadModuleW(const std::shared_ptr ¶ms); + ReturnMessage reloadModuleW(const std::shared_ptr ¶ms); + ReturnMessage reloadAllModulesW(const std::shared_ptr ¶ms); + ReturnMessage checkModuleLoadedW(const std::shared_ptr ¶ms); + ReturnMessage enableModuleW(const std::shared_ptr ¶ms); + ReturnMessage disableModuleW(const std::shared_ptr ¶ms); + ReturnMessage getModuleStatusW(const std::shared_ptr ¶ms); + ReturnMessage getModuleConfigW(const std::shared_ptr ¶ms); + ReturnMessage getModuleListW(const std::shared_ptr ¶ms); - public: + // ------------------------------------------------------------------- + // Message methods + // ------------------------------------------------------------------- + template void MSSubscribe(const std::string &topic, std::function callback, int priority = 0) { @@ -166,6 +216,8 @@ namespace Lithium m_MessageBus->Publish(topic, message); } + ReturnMessage returnMessage(const std::string &message); + public: void addThread(std::function func, const std::string &name); void joinAllThreads(); @@ -180,6 +232,33 @@ namespace Lithium bool runChaiScript(const std::string &filename); void initMyAppChai(); + void LiRegisterFunc(const std::string &name, std::function &)> handler) + { + m_CommandDispatcher->RegisterHandler(name, handler); + } + + template + void LiRegisterMemberFunc(const std::string &name, void (T::*memberFunc)(const std::shared_ptr)) + { + if (!m_CommandDispatcher) + m_CommandDispatcher = std::make_unique>>(); + m_CommandDispatcher->RegisterMemberHandler(name, this, memberFunc); + } + + // Max: The async func will be executed in a separate thread, and the return value will be ignored. + // So must use MessageBus to send the return value. + template + void LiRegisterAsyncMemberFunc(const std::string &name, void (T::*memberFunc)(const std::shared_ptr), bool async = false) + { + if (!m_CommandDispatcher) + m_CommandDispatcher = std::make_unique>>(); + m_CommandDispatcher->RegisterMemberHandler(name + "_async", this, memberFunc); + } + + private: + + std::unique_ptr>> m_CommandDispatcher; + private: std::shared_ptr m_ThreadManager; std::shared_ptr m_ConfigManager; @@ -192,6 +271,7 @@ namespace Lithium std::shared_ptr m_PluginManager; std::shared_ptr m_ScriptManager; std::shared_ptr m_ModuleLoader; + std::shared_ptr m_ErrorStack; }; extern std::shared_ptr MyApp; diff --git a/src/atom/CMakeLists.txt b/src/atom/CMakeLists.txt index 4db04675..b376d438 100644 --- a/src/atom/CMakeLists.txt +++ b/src/atom/CMakeLists.txt @@ -26,9 +26,11 @@ list(APPEND ${PROJECT_NAME}_SOURCES search/sqlite.cpp + server/daemon.cpp server/serialize.cpp server/deserialize.cpp server/json_checker.cpp + server/message.cpp server/xml_checker.cpp server/global_ptr.cpp @@ -41,16 +43,21 @@ list(APPEND ${PROJECT_NAME}_SOURCES system/dirw.cpp type/ini.cpp + type/iparams.cpp thread/thread.cpp + thread/timer.cpp utils/aes.cpp + utils/env.cpp + utils/hash_util.cpp utils/huffman.cpp utils/math.cpp utils/string.cpp utils/static_switch.cpp utils/time.cpp + web/address.cpp web/downloader.cpp web/httpclient.cpp web/httplite.cpp @@ -75,6 +82,7 @@ list(APPEND ${PROJECT_NAME}_HEADERS search/sqlite.hpp + server/daemon.hpp server/serialize.hpp server/deserialize.hpp server/json_checker.hpp @@ -84,6 +92,7 @@ list(APPEND ${PROJECT_NAME}_HEADERS server/message_bus.hpp server/message_queue.hpp server/variables.hpp + server/message.hpp system/system.hpp system/crash.hpp @@ -95,10 +104,14 @@ list(APPEND ${PROJECT_NAME}_HEADERS type/json.hpp type/ini.hpp + type/iparams.hpp + type/small_vector.hpp + type/small_hashmap.hpp thread/async.hpp thread/thread_pool.hpp thread/thread.hpp + thread/timer.hpp utils/aes.hpp utils/exception.hpp @@ -111,7 +124,10 @@ list(APPEND ${PROJECT_NAME}_HEADERS utils/static_switch.hpp utils/switch.hpp utils/time.hpp + utils/env.hpp + utils/hash_util.hpp + web/address.hpp web/downloader.hpp web/httpclient.hpp web/httplite.hpp diff --git a/src/atom/error/error_stack.cpp b/src/atom/error/error_stack.cpp index c1e4c7da..45ccd674 100644 --- a/src/atom/error/error_stack.cpp +++ b/src/atom/error/error_stack.cpp @@ -32,37 +32,65 @@ Description: Error Stack #include "error_stack.hpp" #include +#include + #include "atom/log/loguru.hpp" +#include "atom/utils/time.hpp" std::ostream &operator<<(std::ostream &os, const ErrorInfo &error) { - os << "Error message: " << error.errorMessage << "\n"; - os << "Module name: " << error.moduleName << "\n"; - os << "Timestamp: " << std::asctime(std::localtime(&error.timestamp)) << "\n"; + os << "{" + << "\"errorMessage\": \"" << error.errorMessage << "\"," + << "\"moduleName\": \"" << error.moduleName << "\"," + << "\"functionName\": \"" << error.functionName << "\"," + << "\"line\": " << error.line << "," + << "\"fileName\": \"" << error.fileName << "\"," + << "\"timestamp\": \"" << TimeStampToString(error.timestamp) << "\"," + << "\"uuid\": \"" << error.uuid << "\"" + << "}"; return os; } std::string operator<<(const std::string &str, const ErrorInfo &error) { - std::string output; - output += "Error message: " + error.errorMessage + "\n"; - output += "Module name: " + error.moduleName + "\n"; - output += "Timestamp: " + std::string(std::asctime(std::localtime(&error.timestamp))); - - return output; + std::stringstream ss; + ss << "{" + << "\"errorMessage\": \"" << error.errorMessage << "\"," + << "\"moduleName\": \"" << error.moduleName << "\"," + << "\"functionName\": \"" << error.functionName << "\"," + << "\"line\": " << error.line << "," + << "\"fileName\": \"" << error.fileName << "\"," + << "\"timestamp\": \"" << TimeStampToString(error.timestamp) << "\"," + << "\"uuid\": \"" << error.uuid << "\"" + << "}"; + + return ss.str(); } ErrorStack::ErrorStack() { } -void ErrorStack::InsertError(const std::string &errorMessage, const std::string &moduleName) +std::shared_ptr ErrorStack::createShared() +{ + return std::make_shared(); +} + +std::unique_ptr ErrorStack::createUnique() +{ + return std::make_unique(); +} + +void ErrorStack::InsertError(const std::string &errorMessage, const std::string &moduleName, const std::string &functionName, int line, const std::string &fileName) { ErrorInfo error; error.errorMessage = errorMessage; error.moduleName = moduleName; error.timestamp = std::time(nullptr); + error.functionName = functionName; + error.line = line; + error.fileName = fileName; errorStack.push_back(error); } @@ -101,7 +129,7 @@ std::vector ErrorStack::GetFilteredErrorsByModule(const std::string & return errors; } -void ErrorStack::InsertErrorCompressed(const std::string &errorMessage, const std::string &moduleName) +void ErrorStack::InsertErrorCompressed(const std::string &errorMessage, const std::string &moduleName, const std::string &functionName, int line, const std::string &fileName) { time_t currentTime = std::time(nullptr); @@ -121,6 +149,9 @@ void ErrorStack::InsertErrorCompressed(const std::string &errorMessage, const st error.errorMessage = errorMessage; error.moduleName = moduleName; error.timestamp = currentTime; + error.functionName = functionName; + error.line = line; + error.fileName = fileName; errorStack.push_back(error); } diff --git a/src/atom/error/error_stack.hpp b/src/atom/error/error_stack.hpp index 17d94ba9..662fe5ac 100644 --- a/src/atom/error/error_stack.hpp +++ b/src/atom/error/error_stack.hpp @@ -43,7 +43,11 @@ struct ErrorInfo { std::string errorMessage; /**< Error message. */ std::string moduleName; /**< Module name. */ + std::string functionName; /**< Function name where the error occurred. */ + int line; /**< Line number where the error occurred. */ + std::string fileName; /**< File name where the error occurred. */ time_t timestamp; /**< Timestamp of the error. */ + std::string uuid; /**< UUID of the error. */ }; /** @@ -73,12 +77,24 @@ class ErrorStack */ ErrorStack(); + /** + * @brief Create shared ErrorStack object. + * @return Shared ErrorStack object. + */ + static std::shared_ptr createShared(); + + /** + * @brief Create unique ErrorStack object. + * @return Unique ErrorStack object. + */ + static std::unique_ptr createUnique(); + /** * @brief Insert an error into the stack. * @param errorMessage Error message. * @param moduleName Module name where the error occurred. */ - void InsertError(const std::string &errorMessage, const std::string &moduleName); + void InsertError(const std::string &errorMessage, const std::string &moduleName, const std::string &functionName, int line, const std::string &fileName); /** * @brief Set the filtered modules for printing the error stack. @@ -108,7 +124,7 @@ class ErrorStack * @param errorMessage Error message. * @param moduleName Module name where the error occurred. */ - void InsertErrorCompressed(const std::string &errorMessage, const std::string &moduleName); + void InsertErrorCompressed(const std::string &errorMessage, const std::string &moduleName, const std::string &functionName, int line, const std::string &fileName); /** * @brief Get the compressed errors as a string. diff --git a/src/atom/server/message.cpp b/src/atom/server/message.cpp index f804c14b..39344224 100644 --- a/src/atom/server/message.cpp +++ b/src/atom/server/message.cpp @@ -1,200 +1,181 @@ +/* + * message.cpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-18 + +Description: A message class, which can be used to store different types of messages + +**************************************************/ + #include "message.hpp" #include "atom/utils/time.hpp" +#include "atom/type/iparams.hpp" + +#include "core/property/uuid.hpp" + +using namespace std; + +// Message +Message::Message(Type t, const string &name, const string &target, const string &origin) + : type_(t), name_(name), target_(target), origin_(origin) +{ + timestamp_ = GetChinaTimestampString(); + uuid_ = LITHIUM::UUID::UUIDGenerator::generateUUIDWithFormat(); +} + +Message::Type Message::type() const +{ + return type_; +} + +string_view Message::target() const +{ + return target_; +} + +string_view Message::origin() const +{ + return origin_; +} + +string Message::timestamp() const +{ + // TODO: implement timestamp function + return ""; +} + +string Message::name() const +{ + return name_; +} + +double Message::api_version() const +{ + return api_version_; +} + +// TextMessage +TextMessage::TextMessage(const string &name, const string &text, const string &target, const string &origin) + : Message(Type::kText, name, target, origin), value_(text) +{ +} + +string TextMessage::value() const +{ + return value_; +} + +// NumberMessage +NumberMessage::NumberMessage(const string &name, double number, const string &target, const string &origin) + : Message(Type::kNumber, name, target, origin), value_(number) +{ +} + +double NumberMessage::value() const +{ + return value_; +} -Message::Message(Type t, const std::string &target, const std::string &origin) - : type_(t), target_(target), origin_(origin),timestamp_(GetChinaTimestampString()) {} - -// TextMessage class -class TextMessage : public Message -{ -public: - explicit TextMessage(std::string text) : Message(Type::kText), text_(std::move(text)) {} - const std::string &text() const { return text_; } - void print() const override { std::cout << "Text Message: " << text_ << '\n'; } - -private: - std::string text_; -}; - -// NumberMessage class -class NumberMessage : public Message -{ -public: - explicit NumberMessage(double number) : Message(Type::kNumber), number_(number) {} - double number() const { return number_; } - void print() const override { std::cout << "Number Message: " << number_ << '\n'; } - -private: - double number_; -}; - -// StructuredDataMessage class -class StructuredDataMessage : public Message -{ -public: - explicit StructuredDataMessage(std::vector data) : Message(Type::kStructuredData), data_(std::move(data)) {} - const std::vector &data() const { return data_; } - void print() const override - { - std::cout << "Structured Data Message: "; - for (const auto &num : data_) - { - std::cout << num << ' '; - } - std::cout << '\n'; - } - -private: - std::vector data_; -}; - -// CustomMessage1 class -class CustomMessage1 : public Message -{ -public: - explicit CustomMessage1(std::string data) : Message(Type::kCustom1), data_(std::move(data)) {} - const std::string &data() const { return data_; } - void print() const override { std::cout << "Custom Message 1: " << data_ << '\n'; } - -private: - std::string data_; -}; - -// CustomMessage2 class -class CustomMessage2 : public Message -{ -public: - explicit CustomMessage2(int value) : Message(Type::kCustom2), value_(value) {} - int value() const { return value_; } - void print() const override { std::cout << "Custom Message 2: " << value_ << '\n'; } - -private: - int value_; -}; - -// Message factory function -template -std::unique_ptr MakeMessage(Args &&...args) -{ - return std::make_unique(std::forward(args)...); -} - -// VariantMessage class, using std::unique_ptr to store different message types -class VariantMessage -{ -public: - VariantMessage() : msg_(std::make_unique("")) {} - - template - void Set(T message) - { - msg_ = std::make_unique(std::move(message)); - } - - template - const T &Get() const - { - return static_cast(*msg_); - } - - std::optional type() const - { - if (msg_) - { - return msg_->type(); - } - return std::nullopt; - } - - void reset() - { - msg_ = std::make_unique(""); - } - -private: - std::unique_ptr msg_; -}; - -int main() -{ - // Create different types of messages using the factory function - auto text_msg = MakeMessage("Hello, world!"); - auto number_msg = MakeMessage(3.14); - auto structured_data_msg = MakeMessage(std::vector{1, 2, 3}); - auto custom_msg1 = MakeMessage("Custom message 1"); - auto custom_msg2 = MakeMessage(42); - - // Store messages in VariantMessage - VariantMessage variant_msg; - variant_msg.Set(std::move(*text_msg)); - - // Print the message based on its type - if (auto type = variant_msg.type()) - { - switch (*type) - { - case Message::Type::kText: - variant_msg.Get().print(); - break; - case Message::Type::kNumber: - variant_msg.Get().print(); - break; - case Message::Type::kStructuredData: - variant_msg.Get().print(); - break; - case Message::Type::kCustom1: - variant_msg.Get().print(); - break; - case Message::Type::kCustom2: - variant_msg.Get().print(); - break; - default: - std::cout << "Unknown Message Type\n"; - break; - } - } - else - { - std::cout << "No Message Set\n"; - } - - // Add more messages to VariantMessage - variant_msg.Set(std::move(*number_msg)); - variant_msg.Set(std::move(*structured_data_msg)); - - variant_msg.reset(); - variant_msg.Set(std::move(*structured_data_msg)); - - // Print the new message based on its type - if (auto type = variant_msg.type()) - { - switch (*type) - { - case Message::Type::kText: - variant_msg.Get().print(); - break; - case Message::Type::kNumber: - variant_msg.Get().print(); - break; - case Message::Type::kStructuredData: - variant_msg.Get().print(); - break; - case Message::Type::kCustom1: - variant_msg.Get().print(); - break; - case Message::Type::kCustom2: - variant_msg.Get().print(); - break; - default: - std::cout << "Unknown Message Type\n"; - break; - } - } - else - { - std::cout << "No Message Set\n"; - } - - return 0; +// BooleanMessage +BooleanMessage::BooleanMessage(const string &name, bool value, const string &target, const string &origin) + : Message(Type::kBoolean, name, target, origin), value_(value) +{ +} + +bool BooleanMessage::value() const +{ + return value_; +} + +// AnyMessage +AnyMessage::AnyMessage(const string &name, const any &data, const string &target, const string &origin) + : Message(Type::kAny, name, target, origin), data_(data) +{ + type_ = data_.type().name(); +} + +any AnyMessage::value() const +{ + return data_; +} + +string AnyMessage::type() const +{ + return type_; +} + +// ParamsMessage +ParamsMessage::ParamsMessage(const string &name, shared_ptr params, const string &target, const string &origin) + : Message(Type::kParams, name, target, origin), params_(params) +{ +} + +shared_ptr ParamsMessage::value() const +{ + return params_; +} + +// JsonMessage +JsonMessage::JsonMessage(const string &name, const json &json, const string &target, const string &origin) + : Message(Type::kJson, name, target, origin), value_(json) +{ +} + +json JsonMessage::value() const +{ + return value_; +} + +// MessageHelper +shared_ptr MessageHelper::MakeTextMessage(const string &name, const string &value, const string &target, const string &origin) +{ + return make_shared(name, value, target, origin); +} + +shared_ptr MessageHelper::MakeNumberMessage(const string &name, double value, const string &target, const string &origin) +{ + return make_shared(name, value, target, origin); +} + +shared_ptr MessageHelper::MakeBooleanMessage(const string &name, bool value, const string &target, const string &origin) +{ + return make_shared(name, value, target, origin); +} + +shared_ptr MessageHelper::MakeAnyMessage(const string &name, const any &data, const string &target, const string &origin) +{ + return make_shared(name, data, target, origin); +} + +shared_ptr MessageHelper::MakeParamsMessage(const string &name, shared_ptr params, const string &target, const string &origin) +{ + return make_shared(name, params, target, origin); +} + +shared_ptr MessageHelper::MakeJsonMessage(const string &name, const json &json, const string &target, const string &origin) +{ + return make_shared(name, json, target, origin); } diff --git a/src/atom/server/message.hpp b/src/atom/server/message.hpp index c1930141..59ff6bfa 100644 --- a/src/atom/server/message.hpp +++ b/src/atom/server/message.hpp @@ -1,9 +1,44 @@ +/* + * message.hpp + * + * Copyright (C) 2023 Max Qian + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/************************************************* + +Copyright: 2023 Max Qian. All rights reserved + +Author: Max Qian + +E-mail: astro_air@126.com + +Date: 2023-12-18 + +Description: A message class, which can be used to store different types of messages + +**************************************************/ + #pragma once #include +#include #include -#include -#include +#include "atom/type/json.hpp" + +using json = nlohmann::json; + +class IParams; // Base class Message class Message @@ -17,25 +52,29 @@ class Message kText, kNumber, kBoolean, - kSwitch, kAny, + kParams, + kJson, kMaxType }; - Type type() const { return type_; } - - // Common operations that can be performed on all message types - virtual void serialize() const = 0; + Type type() const; + std::string_view target() const; + std::string_view origin() const; + std::string timestamp() const; + std::string name() const; + double api_version() const; protected: - explicit Message(Type t, const std::string &target, const std::string &origin); + explicit Message(Type t, const std::string &name, const std::string &target, const std::string &origin); private: Type type_; - std::string_view target_; - std::string_view origin_; - - std::string_view timestamp_; + std::string target_; + std::string origin_; + std::string name_; + std::string timestamp_; + std::string uuid_; double api_version_{1.0}; }; @@ -43,66 +82,91 @@ class Message class TextMessage : public Message { public: - explicit TextMessage(std::string text,const std::string &target, const std::string &origin); - void serialize() const override; + explicit TextMessage(const std::string &name, const std::string &text, const std::string &target, const std::string &origin); + + std::string value() const; private: - std::string text_; + std::string value_; }; // NumberMessage class class NumberMessage : public Message { public: - explicit NumberMessage(double number) : Message(Type::kNumber), number_(number) {} - void serialize() const override; + explicit NumberMessage(const std::string &name, double number, const std::string &target, const std::string &origin); + + double value() const; private: - double number_; + double value_; }; -// StructuredDataMessage class -class StructuredDataMessage : public Message +class BooleanMessage : public Message { public: - explicit StructuredDataMessage(std::vector data) : Message(Type::kStructuredData), data_(std::move(data)) {} - void serialize() const override; + explicit BooleanMessage(const std::string &name, bool value, const std::string &target, const std::string &origin); + + bool value() const; private: - std::vector data_; + bool value_; }; -class SwitchMessage : public Message +class AnyMessage : public Message { public: - explicit SwitchMessage(std::string name, std::string value) : Message(Type::kSwitch), name_(std::move(name)), value_(std::move(value)) {} - void serialize() const override; + explicit AnyMessage(const std::string &name, const std::any &data, const std::string &target, const std::string &origin); + + std::any value() const; + std::string type() const; private: - std::string name_; - std::string value_; + std::any data_; + std::string type_; }; -class AnyMessage : public Message +class ParamsMessage : public Message { public: - explicit AnyMessage(std::string data) : Message(Type::kAny), data_(std::move(data)) {} - void serialize() const override; + explicit ParamsMessage(const std::string &name, std::shared_ptr params, const std::string &target, const std::string &origin); + + std::shared_ptr value() const; private: - std::string data_; + std::shared_ptr params_; }; -// Message factory function -template -std::unique_ptr MakeUniqueMessage(Args &&...args) +class JsonMessage : public Message { - return std::make_unique(std::forward(args)...); -} +public: + explicit JsonMessage(const std::string &name, const json &json, const std::string &target, const std::string &origin); + + json value() const; + +private: + json value_; +}; -// Message factory function -template -std::unique_ptr MakeSharedMessage(Args &&...args) +class MessageHelper { - return std::make_shared(std::forward(args)...); -} +public: + static std::shared_ptr MakeTextMessage(const std::string &name, const std::string &value, const std::string &target, const std::string &origin); + + static std::shared_ptr MakeNumberMessage(const std::string &name, double value, const std::string &target, const std::string &origin); + + static std::shared_ptr MakeBooleanMessage(const std::string &name, bool value, const std::string &target, const std::string &origin); + + static std::shared_ptr MakeAnyMessage(const std::string &name, const std::any &data, const std::string &target, const std::string &origin); + + static std::shared_ptr MakeParamsMessage(const std::string &name, std::shared_ptr params, const std::string &target, const std::string &origin); + + static std::shared_ptr MakeJsonMessage(const std::string &name, const json &json, const std::string &target, const std::string &origin); +}; + +using ReturnMessage = std::variant, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr>; \ No newline at end of file diff --git a/src/atom/server/message_bus.hpp b/src/atom/server/message_bus.hpp index 85bb09d7..60d52771 100644 --- a/src/atom/server/message_bus.hpp +++ b/src/atom/server/message_bus.hpp @@ -155,6 +155,24 @@ namespace Lithium subscribersLock_.unlock(); } + template + void UnsubscribeFromNamespace(const std::string &namespaceName, std::function callback) + { + std::string topic = namespaceName + ".*"; + Unsubscribe(topic, callback, namespaceName); + } + + void UnsubscribeAll(const std::string &namespace_ = "") + { + std::string fullTopic = namespace_.empty()? "*" : (namespace_ + "::*"); + + subscribersLock_.lock(); + subscribers_.erase(fullTopic); + subscribersLock_.unlock(); + + DLOG_F(INFO, "Unsubscribed from all topics"); + } + template void Publish(const std::string &topic, const T &message, const std::string &namespace_ = "") { diff --git a/src/atom/thread/daemon.cpp b/src/atom/thread/daemon.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/src/atom/type/iparams.cpp b/src/atom/type/iparams.cpp index 6efc9eff..37787b85 100644 --- a/src/atom/type/iparams.cpp +++ b/src/atom/type/iparams.cpp @@ -47,7 +47,34 @@ bool IParams::serialize(const std::string &filename) const json sectionData; for (const auto &entry : section.second) { - sectionData[entry.first] = entry.second; + if (entry.second.type() == typeid(int)) + { + sectionData[entry.first] = std::any_cast(entry.second); + } + else if (entry.second.type() == typeid(float)) + { + sectionData[entry.first] = std::any_cast(entry.second); + } + else if (entry.second.type() == typeid(double)) + { + sectionData[entry.first] = std::any_cast(entry.second); + } + else if (entry.second.type() == typeid(std::string)) + { + sectionData[entry.first] = std::any_cast(entry.second); + } + else if (entry.second.type() == typeid(const char *)) + { + sectionData[entry.first] = std::any_cast(entry.second); + } + else if (entry.second.type() == typeid(bool)) + { + sectionData[entry.first] = std::any_cast(entry.second); + } + else + { + LOG_F(ERROR, "Unsupported type: {}", entry.second.type().name()); + } } jsonData[section.first] = sectionData; } @@ -140,7 +167,7 @@ std::string IParams::toJson() const } catch (const std::bad_any_cast &e) { - LOG_F(ERROR, "Failed to cast any_cast: {}", e.what()) + LOG_F(ERROR, "Failed to cast any_cast: {}", e.what()); } } jsonData[section.first] = sectionData; diff --git a/src/atom/utils/hash_util.cpp b/src/atom/utils/hash_util.cpp index 55008dd0..418bcdf5 100644 --- a/src/atom/utils/hash_util.cpp +++ b/src/atom/utils/hash_util.cpp @@ -34,9 +34,12 @@ Description: Implementation of murmur3 hash and quick hash #include #include #include +#include #include #include #include +#include +#include #define ROTL(x, r) ((x << r) | (x >> (32 - r))) @@ -261,184 +264,6 @@ std::string base64decode(const std::string &src) return result; } -std::string base64encode(const std::string &data) -{ - return base64encode(data.c_str(), data.size()); -} - -std::string base64encode(const void *data, size_t len) -{ - const char *base64 = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - std::string ret; - ret.reserve(len * 4 / 3 + 2); - - const unsigned char *ptr = (const unsigned char *)data; - const unsigned char *end = ptr + len; - - while (ptr < end) - { - unsigned int packed = 0; - int i = 0; - int padding = 0; - for (; i < 3 && ptr < end; ++i, ++ptr) - { - packed = (packed << 8) | *ptr; - } - if (i == 2) - { - padding = 1; - } - else if (i == 1) - { - padding = 2; - } - for (; i < 3; ++i) - { - packed <<= 8; - } - - ret.append(1, base64[packed >> 18]); - ret.append(1, base64[(packed >> 12) & 0x3f]); - if (padding != 2) - { - ret.append(1, base64[(packed >> 6) & 0x3f]); - } - if (padding == 0) - { - ret.append(1, base64[packed & 0x3f]); - } - ret.append(padding, '='); - } - - return ret; -} - -std::string md5(const std::string &data) -{ - return hexstring_from_data(md5sum(data).c_str(), MD5_DIGEST_LENGTH); -} - -std::string sha1(const std::string &data) -{ - return hexstring_from_data(sha1sum(data).c_str(), SHA_DIGEST_LENGTH); -} - -std::string md5sum(const void *data, size_t len) -{ - MD5_CTX ctx; - MD5_Init(&ctx); - MD5_Update(&ctx, data, len); - std::string result; - result.resize(MD5_DIGEST_LENGTH); - MD5_Final((unsigned char *)&result[0], &ctx); - return result; -} - -std::string md5sum(const std::string &data) -{ - return md5sum(data.c_str(), data.size()); -} - -// std::string sha0sum(const void *data, size_t len) { -// SHA_CTX ctx; -// SHA0_Init(&ctx); -// SHA1_Update(&ctx, data, len); -// std::string result; -// result.resize(SHA_DIGEST_LENGTH); -// SHA1_Final((unsigned char*)&result[0], &ctx); -// return result; -// } - -// std::string sha0sum(const std::string & data) { -// return sha0sum(data.c_str(), data.length()); -// } - -std::string sha1sum(const void *data, size_t len) -{ - SHA_CTX ctx; - SHA1_Init(&ctx); - SHA1_Update(&ctx, data, len); - std::string result; - result.resize(SHA_DIGEST_LENGTH); - SHA1_Final((unsigned char *)&result[0], &ctx); - return result; -} - -std::string sha1sum(const std::string &data) -{ - return sha1sum(data.c_str(), data.size()); -} - -struct xorStruct -{ - xorStruct(char value) : m_value(value) {} - char m_value; - char operator()(char in) const { return in ^ m_value; } -}; - -template -std::string hmac(const std::string &text, const std::string &key) -{ - std::string keyLocal = key; - CTX ctx; - if (keyLocal.size() > B) - { - Init(&ctx); - Update(&ctx, keyLocal.c_str(), keyLocal.size()); - keyLocal.resize(L); - Final((unsigned char *)&keyLocal[0], &ctx); - } - keyLocal.append(B - keyLocal.size(), '\0'); - std::string ipad = keyLocal, opad = keyLocal; - std::transform(ipad.begin(), ipad.end(), ipad.begin(), xorStruct(0x36)); - std::transform(opad.begin(), opad.end(), opad.begin(), xorStruct(0x5c)); - Init(&ctx); - Update(&ctx, ipad.c_str(), B); - Update(&ctx, text.c_str(), text.size()); - std::string result; - result.resize(L); - Final((unsigned char *)&result[0], &ctx); - Init(&ctx); - Update(&ctx, opad.c_str(), B); - Update(&ctx, result.c_str(), L); - Final((unsigned char *)&result[0], &ctx); - return result; -} - -std::string hmac_md5(const std::string &text, const std::string &key) -{ - return hmac(text, key); -} - -std::string hmac_sha1(const std::string &text, const std::string &key) -{ - return hmac(text, key); -} - -std::string -hmac_sha256(const std::string &text, const std::string &key) -{ - return hmac(text, key); -} - void hexstring_from_data(const void *data, size_t len, char *output) { const unsigned char *buf = (const unsigned char *)data; @@ -456,7 +281,7 @@ void hexstring_from_data(const void *data, size_t len, char *output) } std::string -hexstring_from_data(const void *data, size_t len) +hexstring_from_data(const char *data, size_t len) { if (len == 0) { diff --git a/src/atom/utils/hash_util.hpp b/src/atom/utils/hash_util.hpp index 702edd34..6c1c082f 100644 --- a/src/atom/utils/hash_util.hpp +++ b/src/atom/utils/hash_util.hpp @@ -92,92 +92,6 @@ uint32_t quick_hash(const char* str); */ uint32_t quick_hash(const void* str, uint32_t size); -/** - * @brief Decodes a Base64 encoded string. - * - * @param src The Base64 encoded string. - * @return std::string The decoded string. - */ -std::string base64decode(const std::string& src); - -/** - * @brief Encodes a string as Base64. - * - * @param data The input string. - * @return std::string The Base64 encoded string. - */ -std::string base64encode(const std::string& data); - -/** - * @brief Encodes a data buffer as Base64. - * - * @param data The input data buffer. - * @param len The length of the data buffer. - * @return std::string The Base64 encoded string. - */ -std::string base64encode(const void* data, size_t len); - -/** - * @brief Calculates the MD5 hash value for a given string. - * - * @param data The input string. - * @return std::string The MD5 hash value in hexadecimal format. - */ -std::string md5(const std::string& data); - -/** - * @brief Calculates the SHA1 hash value for a given string. - * - * @param data The input string. - * @return std::string The SHA1 hash value in hexadecimal format. - */ -std::string sha1(const std::string& data); - -/** - * @brief Calculates the MD5 hash value for a given data buffer. - * - * @param data The input data buffer. - * @param len The length of the data buffer. - * @return std::string The MD5 hash value as raw binary data. - */ -std::string md5sum(const void* data, size_t len); - -/** - * @brief Calculates the SHA1 hash value for a given data buffer. - * - * @param data The input data buffer. - * @param len The length of the data buffer. - * @return std::string The SHA1 hash value as raw binary data. - */ -std::string sha1sum(const void* data, size_t len); - -/** - * @brief Calculates the HMAC-MD5 hash value for a given text and key. - * - * @param text The input text. - * @param key The HMAC key. - * @return std::string The HMAC-MD5 hash value in hexadecimal format. - */ -std::string hmac_md5(const std::string& text, const std::string& key); - -/** - * @brief Calculates the HMAC-SHA1 hash value for a given text and key. - * - * @param text The input text. - * @param key The HMAC key. - * @return std::string The HMAC-SHA1 hash value in hexadecimal format. - */ -std::string hmac_sha1(const std::string& text, const std::string& key); - -/** - * @brief Calculates the HMAC-SHA256 hash value for a given text and key. - * - * @param text The input text. - * @param key The HMAC key. - * @return std::string The HMAC-SHA256 hash value in hexadecimal format. - */ -std::string hmac_sha256(const std::string& text, const std::string& key); - /** * @brief Converts binary data to a hexadecimal string representation. * diff --git a/src/atom/utils/string.cpp b/src/atom/utils/string.cpp index 173fc2fd..2a7f0b1c 100644 --- a/src/atom/utils/string.cpp +++ b/src/atom/utils/string.cpp @@ -30,39 +30,43 @@ Description: Some useful string functions **************************************************/ #include "string.hpp" + #include +#include +#include -bool HasUppercase(const std::string& str) +bool HasUppercase(const std::string &str) { // 判断字符串中是否存在大写字母 // 参数: // - str: 要检查的字符串 // 返回值: // - 如果字符串中存在大写字母,返回true;否则,返回false - return std::any_of(str.begin(), str.end(), [](char ch) { return std::isupper(ch); }); + return std::any_of(str.begin(), str.end(), [](char ch) + { return std::isupper(ch); }); } -std::string ToUnderscore(const std::string& str) +std::string ToUnderscore(const std::string &str) { - std::string result; // 生成一个空字符串用于存储转换结果 - for (char ch : str) // 遍历输入字符串中的每个字符 + std::string result; // 生成一个空字符串用于存储转换结果 + for (char ch : str) // 遍历输入字符串中的每个字符 { - if (std::isupper(ch)) // 如果字符是大写字母 + if (std::isupper(ch)) // 如果字符是大写字母 { - result += '_'; // 在结果字符串前加上下划线 - result += std::tolower(ch); // 将大写字母转换为小写字母并添加到结果字符串 + result += '_'; // 在结果字符串前加上下划线 + result += std::tolower(ch); // 将大写字母转换为小写字母并添加到结果字符串 } - else // 如果字符不是大写字母 + else // 如果字符不是大写字母 { - result += ch; // 直接将字符添加到结果字符串 + result += ch; // 直接将字符添加到结果字符串 } } - return result; // 返回转换后的字符串结果 + return result; // 返回转换后的字符串结果 } -std::string ToCamelCase(const std::string& str) +std::string ToCamelCase(const std::string &str) { - std::string result; // 用于保存转换后的字符串结果 + std::string result; // 用于保存转换后的字符串结果 bool capitalize = false; // 用于标记是否需要大写转换 for (char ch : str) // 遍历输入字符串 @@ -76,7 +80,7 @@ std::string ToCamelCase(const std::string& str) if (capitalize) // 如果需要大写转换 { result += std::toupper(ch); // 将字符转换为大写并添加到结果字符串 - capitalize = false; // 清除大写标记 + capitalize = false; // 清除大写标记 } else // 如果不需要大写转换 { @@ -87,12 +91,12 @@ std::string ToCamelCase(const std::string& str) return result; // 返回转换后的字符串结果 } -std::string ConvertToUnderscore(const std::string& str) +std::string ConvertToUnderscore(const std::string &str) { return HasUppercase(str) ? ToUnderscore(str) : str; } -std::string ConvertToCamelCase(const std::string& str) +std::string ConvertToCamelCase(const std::string &str) { if (str.find('_') != std::string::npos) { @@ -105,3 +109,59 @@ std::string ConvertToCamelCase(const std::string& str) return result; } } + +std::string UrlEncode(const std::string &str) +{ + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : str) + { + if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') + { + escaped << c; + } + else if (c == ' ') + { + escaped << '+'; + } + else + { + escaped << '%' << std::setw(2) << int((unsigned char)c); + } + } + + return escaped.str(); +} + +std::string UrlDecode(const std::string &str) +{ + std::string result; + for (size_t i = 0; i < str.size(); ++i) + { + if (str[i] == '%') + { + int value; + std::istringstream is(str.substr(i + 1, 2)); + if (is >> std::hex >> value) + { + result += static_cast(value); + i += 2; + } + else + { + throw std::invalid_argument("UrlDecode failed"); + } + } + else if (str[i] == '+') + { + result += ' '; + } + else + { + result += str[i]; + } + } + return result; +} diff --git a/src/atom/utils/string.hpp b/src/atom/utils/string.hpp index 54108f63..44801b75 100644 --- a/src/atom/utils/string.hpp +++ b/src/atom/utils/string.hpp @@ -29,8 +29,7 @@ Description: Some useful string functions **************************************************/ -#ifndef UNDERSCORE_H -#define UNDERSCORE_H +#pragma once #include @@ -39,5 +38,5 @@ std::string ToUnderscore(const std::string& str); std::string ToCamelCase(const std::string& str); std::string ConvertToUnderscore(const std::string& str); std::string ConvertToCamelCase(const std::string& str); - -#endif // UNDERSCORE_H +std::string UrlEncode(const std::string &str); +std::string UrlDecode(const std::string &str); diff --git a/src/atom/utils/time.cpp b/src/atom/utils/time.cpp index 81b81c64..4ef2b0f7 100644 --- a/src/atom/utils/time.cpp +++ b/src/atom/utils/time.cpp @@ -84,4 +84,11 @@ std::string GetChinaTimestampString() ss << std::put_time(std::localtime(&local_time), "%Y-%m-%d %H:%M:%S"); return ss.str(); +} + +std::string TimeStampToString(time_t timestamp) +{ + char buffer[80]; + std::strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", std::localtime(×tamp)); + return std::string(buffer); } \ No newline at end of file diff --git a/src/atom/utils/time.hpp b/src/atom/utils/time.hpp index eb8f38c9..ed8e01c6 100644 --- a/src/atom/utils/time.hpp +++ b/src/atom/utils/time.hpp @@ -54,3 +54,11 @@ std::string ConvertToChinaTime(const std::string& utcTimeStr); * @return std::string 返回格式为"%Y-%m-%d %H:%M:%S"的东八区字符串时间戳 */ std::string GetChinaTimestampString(); + +/** + * @brief 将时间戳转换为字符串 + * + * @param timestamp 时间戳 + * @return std::string 时间戳对应的字符串 + */ +std::string TimeStampToString(time_t timestamp); diff --git a/src/atom/web/address.hpp b/src/atom/web/address.hpp index 7e2d22c4..d9818178 100644 --- a/src/atom/web/address.hpp +++ b/src/atom/web/address.hpp @@ -25,13 +25,20 @@ template static uint32_t CountBytes(T value) { uint32_t result = 0; - for (; value; ++result) + uint8_t *ptr = reinterpret_cast(&value); + for (size_t i = 0; i < sizeof(T); ++i) { - value &= value - 1; + uint8_t byte = ptr[i]; + for (; byte; ++result) + { + byte &= byte - 1; + } } return result; } +class IPAddress; + class Address { public: diff --git a/src/device/device_manager.cpp b/src/device/device_manager.cpp index c2df5a31..fe6c4209 100644 --- a/src/device/device_manager.cpp +++ b/src/device/device_manager.cpp @@ -141,6 +141,48 @@ namespace Lithium return std::make_unique(messageBus, configManager); } + void DeviceManager::connectToMessageBus() + { + m_MessageBus->Subscribe>("device", [this](std::shared_ptr message) -> void { + switch (message->type()) + { + case Message::Type::kNumber: + { + std::shared_ptr numberMessage = std::dynamic_pointer_cast(message); + + break; + } + case Message::Type::kText: + { + std::shared_ptr stringMessage = std::dynamic_pointer_cast(message); + break; + } + case Message::Type::kBoolean: + { + std::shared_ptr booleanMessage = std::dynamic_pointer_cast(message); + break; + } + case Message::Type::kParams: + { + std::shared_ptr paramsMessage = std::dynamic_pointer_cast(message); + std::shared_ptr params = paramsMessage->value(); + + break; + } + case Message::Type::kJson: + { + std::shared_ptr jsonMessage = std::dynamic_pointer_cast(message); + break; + } + default: + { + LOG_F(ERROR, "Unknown message type {}", magic_enum::enum_name(message->type())); + break; + } + } + }); + } + std::vector DeviceManager::getDeviceList(DeviceType type) { std::vector deviceList; diff --git a/src/device/device_manager.hpp b/src/device/device_manager.hpp index d586178b..1bbf6a3e 100644 --- a/src/device/device_manager.hpp +++ b/src/device/device_manager.hpp @@ -39,6 +39,7 @@ Description: Device Manager #include "core/device.hpp" #include "core/device_type.hpp" +#include "atom/server/message.hpp" #include "error/error_code.hpp" @@ -107,7 +108,7 @@ namespace Lithium // Message methods // ------------------------------------------------------------------- - std::function connectToMessageBus(); + void connectToMessageBus(); // ------------------------------------------------------------------- // Device methods diff --git a/src/task/task_stack.hpp b/src/task/task_stack.hpp index e0e9309c..7d12558a 100644 --- a/src/task/task_stack.hpp +++ b/src/task/task_stack.hpp @@ -36,10 +36,11 @@ Description: Task Stack #include #if ENABLE_FASTHASH #include "emhash/hash_table8.hpp" +#include "emhash/hash_set8.hpp" #else #include -#endif #include +#endif #include #include "core/property/task/task.hpp" @@ -48,7 +49,9 @@ enum class TaskStatus { Pending, Executing, - Completed + Completed, + Failed, + Cancelled }; namespace Lithium::Task @@ -96,8 +99,11 @@ namespace Lithium::Task std::vector> tasks_; /**< The tasks in the task stack. */ std::vector task_status_; /**< The status of each task in the task stack. */ std::vector task_names_; /**< The names of the tasks in the task stack. */ +#ifdef ENABLE_FASTHASH + emhash8::hashMap> mutually_exclusive_tasks_; /**< The map of mutually exclusive tasks. */ +#else std::unordered_map> mutually_exclusive_tasks_; /**< The map of mutually exclusive tasks. */ - +#endif /** * @brief Checks if a task with the given name exists in the task stack. * @param taskName The name of the task to check.

    Hnc%T~cAgq~15uwJ&F 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