From 3f36f8cc30b3f48ea2061b6d9eff28b27ec545d0 Mon Sep 17 00:00:00 2001 From: Leon Kleinschmidt <101114069+LeoKle@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:47:39 +0200 Subject: [PATCH] v1.3.0 (#43) major refactor to improve performance and remove technical debt --- .clang-format | 3 + .gitattributes | 2 - .github/workflows/build-develop.yml | 96 +++ .github/workflows/build-release.yml | 89 +++ .github/workflows/clang-format.yml | 15 + .github/workflows/cmake_project_version.py | 25 + .github/workflows/determine_dev_release.py | 57 ++ .github/workflows/version_handler.py | 34 + CMakeLists.txt | 172 +++-- CMakeSettings.json | 11 +- com/Airport.cpp | 674 ----------------- com/Airport.h | 77 -- com/PerformanceLock.h | 35 - com/Server.cpp | 379 ---------- com/Server.h | 77 -- config/FileFormat.cpp | 137 ---- config/FileFormat.h | 52 -- helper/String.h | 120 --- logging/Logger.cpp | 63 -- logging/Logger.h | 33 - logging/Performance.h | 67 -- Version.h.in => src/Version.h.in | 27 +- src/config/ConfigParser.cpp | 136 ++++ src/config/ConfigParser.h | 25 + src/config/PluginConfig.h | 26 + src/config/vacdm.txt | 13 + src/core/DataManager.cpp | 528 +++++++++++++ src/core/DataManager.h | 117 +++ src/core/Server.cpp | 496 +++++++++++++ src/core/Server.h | 87 +++ src/core/TagFunctions.h | 218 ++++++ src/core/TagItems.h | 136 ++++ src/core/TagItemsColor.h | 305 ++++++++ src/log/Logger.cpp | 204 ++++++ src/log/Logger.h | 88 +++ {logging => src/log}/sqlite3.c | 0 {logging => src/log}/sqlite3.h | 0 {logging => src/log}/sqlite3ext.h | 0 main.cpp => src/main.cpp | 33 +- src/types/Ecfmp.h | 36 + types/Flight.h => src/types/Pilot.h | 116 ++- src/utils/Date.h | 113 +++ src/utils/Number.h | 9 + src/utils/String.h | 113 +++ src/vACDM.cpp | 263 +++++++ src/vACDM.h | 42 ++ types/SystemConfig.h | 25 - ui/color.cpp | 311 -------- ui/color.h | 34 - vACDM.cpp | 814 --------------------- vACDM.h | 99 --- 51 files changed, 3452 insertions(+), 3180 deletions(-) create mode 100644 .clang-format delete mode 100644 .gitattributes create mode 100644 .github/workflows/build-develop.yml create mode 100644 .github/workflows/build-release.yml create mode 100644 .github/workflows/clang-format.yml create mode 100644 .github/workflows/cmake_project_version.py create mode 100644 .github/workflows/determine_dev_release.py create mode 100644 .github/workflows/version_handler.py delete mode 100644 com/Airport.cpp delete mode 100644 com/Airport.h delete mode 100644 com/PerformanceLock.h delete mode 100644 com/Server.cpp delete mode 100644 com/Server.h delete mode 100644 config/FileFormat.cpp delete mode 100644 config/FileFormat.h delete mode 100644 helper/String.h delete mode 100644 logging/Logger.cpp delete mode 100644 logging/Logger.h delete mode 100644 logging/Performance.h rename Version.h.in => src/Version.h.in (66%) create mode 100644 src/config/ConfigParser.cpp create mode 100644 src/config/ConfigParser.h create mode 100644 src/config/PluginConfig.h create mode 100644 src/config/vacdm.txt create mode 100644 src/core/DataManager.cpp create mode 100644 src/core/DataManager.h create mode 100644 src/core/Server.cpp create mode 100644 src/core/Server.h create mode 100644 src/core/TagFunctions.h create mode 100644 src/core/TagItems.h create mode 100644 src/core/TagItemsColor.h create mode 100644 src/log/Logger.cpp create mode 100644 src/log/Logger.h rename {logging => src/log}/sqlite3.c (100%) rename {logging => src/log}/sqlite3.h (100%) rename {logging => src/log}/sqlite3ext.h (100%) rename main.cpp => src/main.cpp (57%) create mode 100644 src/types/Ecfmp.h rename types/Flight.h => src/types/Pilot.h (62%) create mode 100644 src/utils/Date.h create mode 100644 src/utils/Number.h create mode 100644 src/utils/String.h create mode 100644 src/vACDM.cpp create mode 100644 src/vACDM.h delete mode 100644 types/SystemConfig.h delete mode 100644 ui/color.cpp delete mode 100644 ui/color.h delete mode 100644 vACDM.cpp delete mode 100644 vACDM.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d53bf86 --- /dev/null +++ b/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: Google +IndentWidth: 4 +ColumnLimit: 120 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index b1c4771..0000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -*.cpp linguist-language=cpp -*.h linguist-language=cpp \ No newline at end of file diff --git a/.github/workflows/build-develop.yml b/.github/workflows/build-develop.yml new file mode 100644 index 0000000..d443b40 --- /dev/null +++ b/.github/workflows/build-develop.yml @@ -0,0 +1,96 @@ +name: Build Plugin Dev Release + +on: + push: + branches: + - "develop" + +env: + DLL_NAME: vACDM.dll + BUILD_CONFIGURATION: Release + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: "0" + DEV_RELEASE: "-1" + +jobs: + formatting-check: + uses: ./.github/workflows/clang-format.yml + + build: + needs: formatting-check + runs-on: windows-latest + name: "Build plugin" + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.x" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install PyGithub + + - name: Run version handler + env: + REPOSITORY: ${{ github.repository }} + REF: ${{ github.ref }} + run: | + python .github/workflows/version_handler.py + + - name: Determine DEV_RELEASE value + id: find_latest_dev_release + env: + REPOSITORY: ${{ github.repository }} + run: python .github/workflows/determine_dev_release.py + + - name: Install MSVC toolset + run: choco install visualstudio2019buildtools --package-parameters "--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64" + + - name: Set up MSVC environment + shell: pwsh + run: | + Import-Module "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" + Enter-VsDevShell -VsInstallPath "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools" + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_CONFIGURATION}} -A Win32 -DDEV_BUILD=ON -DDEV_RELEASE_NUMBER="${{ env.DEV_RELEASE }}" + + - name: Build DLL + run: cmake --build build --config ${{env.BUILD_CONFIGURATION}} + + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v1 + with: + tag_name: v${{ env.VERSION }} + release_name: v${{ env.VERSION }} + draft: true # to allow amendment of release notes + prerelease: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload release asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: build/${{env.BUILD_CONFIGURATION}}/${{env.DLL_NAME}} + asset_name: ${{env.DLL_NAME}} + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload config file (vacdm.txt) + id: upload-release-asset-txt + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: src/config/vacdm.txt + asset_name: vacdm.txt + asset_content_type: text/plain + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 0000000..0e6f2cb --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,89 @@ +name: Build Plugin Release + +on: + push: + branches: + - main + +env: + DLL_NAME: vACDM.dll + BUILD_CONFIGURATION: Release + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: "0" + +jobs: + formatting-check: + uses: ./.github/workflows/clang-format.yml + + build: + needs: formatting-check + runs-on: windows-latest + name: "Build plugin" + + steps: + - uses: actions/checkout@v3 + + - name: Install MSVC toolset + run: choco install visualstudio2019buildtools --package-parameters "--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64" + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.x" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install PyGithub + + - name: Run version handler + env: + REPOSITORY: ${{ github.repository }} + REF: ${{ github.ref }} + run: | + python .github/workflows/version_handler.py + + - name: Set up MSVC environment + shell: pwsh + run: | + Import-Module "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" + Enter-VsDevShell -VsInstallPath "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools" + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_CONFIGURATION}} -A Win32 + + - name: Build DLL + run: cmake --build build --config ${{env.BUILD_CONFIGURATION}} + + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v1 + with: + tag_name: v${{ env.VERSION }} + release_name: v${{ env.VERSION }} + draft: true # to allow amendment of release notes + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload release asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: build/${{env.BUILD_CONFIGURATION}}/${{env.DLL_NAME}} + asset_name: ${{env.DLL_NAME}} + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload config file (vacdm.txt) + id: upload-release-asset-txt + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: src/config/vacdm.txt + asset_name: vacdm.txt + asset_content_type: text/plain + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..7e42428 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,15 @@ +name: clang-format Check +on: [workflow_call, pull_request] +jobs: + formatting-check: + name: Formatting Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run clang-format style check for C/C++ programs. + uses: jidicula/clang-format-action@v4.11.0 + with: + clang-format-version: "13" + check-path: "src" + fallback-style: "Google" + exclude-regex: "(sqlite3)" diff --git a/.github/workflows/cmake_project_version.py b/.github/workflows/cmake_project_version.py new file mode 100644 index 0000000..3a2f211 --- /dev/null +++ b/.github/workflows/cmake_project_version.py @@ -0,0 +1,25 @@ +""" Finds the project version defined in CMakeLists.txt""" + +import sys +import re + + +def extract_version_from_cmakelists(): + # Define the pattern to search for in the file + pattern = r'PROJECT\(.*?VERSION\s+"(\d+\.\d+\.\d+)"' + + # Read the contents of CMakeLists.txt + with open("CMakeLists.txt", "r") as file: + contents = file.read() + + # Search for the pattern + match = re.search(pattern, contents) + + # If a match is found, extract the version + if match: + print("Found version number in CMakeLists.txt: ", match.group(1)) + return match.group(1) + + # exit if the version could not be found + print("Could not find version in CMakeLists.txt") + sys.exit(1) diff --git a/.github/workflows/determine_dev_release.py b/.github/workflows/determine_dev_release.py new file mode 100644 index 0000000..e0c3617 --- /dev/null +++ b/.github/workflows/determine_dev_release.py @@ -0,0 +1,57 @@ +import sys +import re +from github import Github + + +def parse_semantic_version(version): + # Parse the semantic version and extract major and minor versions + match = re.match(r"v?(\d+)\.(\d+)\.(\d+)", version) + if match: + print(match.groups()) + major, minor, patch = match.groups() + return int(major), int(minor), int(patch) + else: + return None + + +def find_highest_dev_release(repo, major, minor): + # Initialize a GitHub instance + g = Github() + + # Get the repository object + repository = g.get_repo(repo) + + # Fetch all releases + releases = repository.get_releases() + + # Filter pre-releases with matching major and minor versions in their titles + pre_releases = [ + release + for release in releases + if release.prerelease and release.title.startswith(f"v{major}.{minor}") + ] + + # Extract DEV_RELEASE numbers from titles + dev_releases = [int(release.title.split(".")[-1]) for release in pre_releases] + + # Find the highest DEV_RELEASE number + highest_dev_release = max(dev_releases, default=0) + + return highest_dev_release + + +def determine_dev_release(version, repository): + if version == "0": + result = 0 + else: + # Parse the semantic version to extract major and minor versions + major, minor, _ = parse_semantic_version(version) + if major is not None and minor is not None: + result = find_highest_dev_release(repository, major, minor) + else: + print("Invalid semantic version format") + sys.exit(1) + + print("Determined dev release version as ", result) + + return result diff --git a/.github/workflows/version_handler.py b/.github/workflows/version_handler.py new file mode 100644 index 0000000..3969190 --- /dev/null +++ b/.github/workflows/version_handler.py @@ -0,0 +1,34 @@ +""" """ + +import sys +import os +import re + +from cmake_project_version import extract_version_from_cmakelists +from determine_dev_release import determine_dev_release + +REPOSITORY_NAME = os.environ.get("REPOSITORY") +REF = os.environ.get("REF") + +# determine the branch that triggered the workflow +match = re.match(r"refs/heads/(.*)", REF) +if not match: + sys.exit(1) + +branch_name = match.group(1) +print("Determined branch name: ", branch_name) + +version = extract_version_from_cmakelists() + +is_dev_branch = branch_name == "develop" +dev_release = None +if is_dev_branch: + last_dev_release = determine_dev_release(version, REPOSITORY_NAME) + dev_release = str(last_dev_release + 1) + version += "-dev." + dev_release + +# Write the version and dev release to GitHub environment variable +with open(os.getenv("GITHUB_ENV"), "a") as env_file: + env_file.write(f"VERSION={version}\n") + if is_dev_branch and dev_release: + env_file.write(f"DEV_RELEASE={dev_release}\n") diff --git a/CMakeLists.txt b/CMakeLists.txt index 5973101..08f6d46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,87 +1,85 @@ -# Author: -# Sven Czarnian -# License: -# GPLv3 -# Brief: -# Creates the vACMD EuroScope plugin - -CMAKE_MINIMUM_REQUIRED(VERSION 3.14) - -# define the project -PROJECT(vACDM LANGUAGES C CXX VERSION "1.0.0") -SET_PROPERTY(GLOBAL PROPERTY USE_FOLDERS ON) -SET(CMAKE_CXX_STANDARD 20) -SET(CMAKE_CXX_STANDARD_REQUIRED ON) -SET(CMAKE_CXX_EXTENSIONS OFF) -SET(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) -IF (MSVC) - IF (CMAKE_CXX_FLAGS MATCHES "/W[0-4]") - STRING(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - ELSE () - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") - ENDIF () - IF (NOT CMAKE_CXX_FLAGS MATCHES "/MP") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") - ENDIF () - - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /sdl /permissive- /DNOMINMAX") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /sdl /permissive- /DNOMINMAX") - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /MANIFESTUAC:NO /ignore:4099") - ADD_DEFINITIONS(/D_USRDLL) -ENDIF () - -CONFIGURE_FILE( - ${CMAKE_SOURCE_DIR}/Version.h.in - ${CMAKE_BINARY_DIR}/Version.h -) - -# add the binary, include and installation directories to the search paths -INCLUDE_DIRECTORIES( - ${CMAKE_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/external/include - ${CMAKE_SOURCE_DIR} -) - -ADD_DEFINITIONS( - -D_CRT_SECURE_NO_WARNINGS - -DSQLITE_THREADSAFE=0 - -DSQLITE_DEFAULT_FILE_FORMAT=4 - -DSQLITE_DEFAULT_SYNCHRONOUS=0 - -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=0 - -DSQLITE_WIN32_MALLOC - -DSQLITE_THREADSAFE=0 -) - -SET(SOURCE_FILES - config/FileFormat.cpp - config/FileFormat.h - com/Airport.cpp - com/Airport.h - com/PerformanceLock.h - com/Server.cpp - com/Server.h - logging/Logger.cpp - logging/Logger.h - logging/Performance.h - logging/sqlite3.c - logging/sqlite3.h - logging/sqlite3ext.h - types/Flight.h - types/SystemConfig.h - ui/color.cpp - ui/color.h - main.cpp - vACDM.cpp - vACDM.h - Version.h -) - -ADD_LIBRARY(vACDM SHARED ${SOURCE_FILES}) -TARGET_LINK_LIBRARIES(vACDM ${CMAKE_SOURCE_DIR}/external/lib/EuroScopePlugInDLL.lib crypt32.lib ws2_32.lib Shlwapi.lib) -TARGET_LINK_LIBRARIES(vACDM debug ${CMAKE_SOURCE_DIR}/external/lib/jsoncpp_d.lib) -TARGET_LINK_LIBRARIES(vACDM debug ${CMAKE_SOURCE_DIR}/external/lib/libcurl-d.lib) -TARGET_LINK_LIBRARIES(vACDM debug ${CMAKE_SOURCE_DIR}/external/lib/Geographic_d.lib) -TARGET_LINK_LIBRARIES(vACDM optimized ${CMAKE_SOURCE_DIR}/external/lib/jsoncpp.lib) -TARGET_LINK_LIBRARIES(vACDM optimized ${CMAKE_SOURCE_DIR}/external/lib/libcurl.lib) -TARGET_LINK_LIBRARIES(vACDM optimized ${CMAKE_SOURCE_DIR}/external/lib/Geographic.lib) +CMAKE_MINIMUM_REQUIRED(VERSION 3.14) + +PROJECT(vACDM VERSION "1.3.0") +SET_PROPERTY(GLOBAL PROPERTY USE_FOLDERS ON) +SET(CMAKE_CXX_STANDARD 20) +SET(CMAKE_CXX_STANDARD_REQUIRED ON) +SET(CMAKE_CXX_EXTENSIONS OFF) +SET(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) +IF (MSVC) + IF (CMAKE_CXX_FLAGS MATCHES "/W[0-4]") + STRING(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + ELSE () + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + ENDIF () + IF (NOT CMAKE_CXX_FLAGS MATCHES "/MP") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") + ENDIF () + + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /sdl /permissive- /DNOMINMAX") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /sdl /permissive- /DNOMINMAX") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /MANIFESTUAC:NO /ignore:4099") + ADD_DEFINITIONS(/D_USRDLL) +ENDIF () + +if(DEV_BUILD) + ADD_DEFINITIONS(-DDEV_BUILD) +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + ADD_DEFINITIONS(-DDEBUG_BUILD=1) # enables log output to console window + ADD_DEFINITIONS(-DDEV_BUILD) +endif() + +CONFIGURE_FILE( + ${CMAKE_SOURCE_DIR}/src/Version.h.in + ${CMAKE_BINARY_DIR}/Version.h +) + +INCLUDE_DIRECTORIES( + ${CMAKE_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/src/ + ${CMAKE_SOURCE_DIR}/external/include + ${CMAKE_SOURCE_DIR} +) + +ADD_DEFINITIONS( + -D_CRT_SECURE_NO_WARNINGS + -DSQLITE_THREADSAFE=0 + -DSQLITE_DEFAULT_FILE_FORMAT=4 + -DSQLITE_DEFAULT_SYNCHRONOUS=0 + -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=0 + -DSQLITE_WIN32_MALLOC + -DSQLITE_THREADSAFE=0 +) + +SET(SOURCE_FILES + src/config/ConfigParser.cpp + src/config/ConfigParser.h + src/core/DataManager.cpp + src/core/DataManager.h + src/core/Server.cpp + src/core/Server.h + src/log/Logger.cpp + src/log/Logger.h + src/log/sqlite3.c + src/log/sqlite3.h + src/log/sqlite3ext.h + src/vACDM.cpp + src/vACDM.h + src/main.cpp + src/Version.h +) + +ADD_LIBRARY(vACDM SHARED ${SOURCE_FILES}) +TARGET_LINK_LIBRARIES(vACDM ${CMAKE_SOURCE_DIR}/external/lib/EuroScopePlugInDLL.lib crypt32.lib ws2_32.lib Shlwapi.lib) +TARGET_LINK_LIBRARIES(vACDM debug ${CMAKE_SOURCE_DIR}/external/lib/jsoncpp_d.lib) +TARGET_LINK_LIBRARIES(vACDM debug ${CMAKE_SOURCE_DIR}/external/lib/libcurl-d.lib) +TARGET_LINK_LIBRARIES(vACDM debug ${CMAKE_SOURCE_DIR}/external/lib/Geographic_d.lib) +TARGET_LINK_LIBRARIES(vACDM optimized ${CMAKE_SOURCE_DIR}/external/lib/jsoncpp.lib) +TARGET_LINK_LIBRARIES(vACDM optimized ${CMAKE_SOURCE_DIR}/external/lib/libcurl.lib) +TARGET_LINK_LIBRARIES(vACDM optimized ${CMAKE_SOURCE_DIR}/external/lib/Geographic.lib) + +# move config file to output dir, allows loading of DLL from output dir +configure_file(${CMAKE_SOURCE_DIR}/src/config/vacdm.txt ${CMAKE_BINARY_DIR}/vacdm.txt COPY) \ No newline at end of file diff --git a/CMakeSettings.json b/CMakeSettings.json index 82577b4..d59dcf7 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -17,11 +17,16 @@ "configurationType": "Debug", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "", + "cmakeCommandArgs": "-DDEV_BUILD=ON", "ctestCommandArgs": "", "inheritEnvironments": ["msvc_x86"], - "variables": [] + "variables": [ + { + "name": "DEV_RELEASE_NUMBER", + "value": "-1", + "type": "STRING" + } + ] } ] } diff --git a/com/Airport.cpp b/com/Airport.cpp deleted file mode 100644 index 5a60c2f..0000000 --- a/com/Airport.cpp +++ /dev/null @@ -1,674 +0,0 @@ -#include - -#include "Airport.h" -#include "logging/Logger.h" -#include "Server.h" - -using namespace std::chrono_literals; -using namespace vacdm; -using namespace vacdm::com; - -static constexpr std::size_t FlightConsolidated = 0; -static constexpr std::size_t FlightEuroscope = 1; -static constexpr std::size_t FlightServer = 2; - -Airport::Airport() : - m_airport(), - m_worker(), - m_pause(false), - m_lock("EMPTY"), - m_flights(), - m_stop(false), - m_manualUpdatePerformance("EMPTY"), - m_workerAllFlightsPerformance("EMPTY"), - m_workerUpdateFlightsPerformance("EMPTY"), - m_asynchronousMessageLock("EMPTY"), - m_asynchronousMessages() { } - -Airport::Airport(const std::string& airport) : - m_airport(airport), - m_worker(), - m_pause(false), - m_lock(airport), - m_flights(), - m_stop(false), - m_manualUpdatePerformance("ManualUpdates" + airport), - m_workerAllFlightsPerformance("AllFlightsRequest" + airport), - m_workerUpdateFlightsPerformance("UpdateFlights" + airport), - m_asynchronousMessageLock("AsyncMessages" + airport), - m_asynchronousMessages() { - const auto flights = Server::instance().allFlights(this->m_airport); - for (const auto& flight : std::as_const(flights)) { - this->m_flights.insert({ flight.callsign, { flight, types::Flight_t(), flight } }); - } - - this->m_worker = std::thread(&Airport::run, this); -} - -Airport::~Airport() { - this->m_stop = true; - this->m_worker.join(); -} - -void Airport::pause() { - this->m_pause = true; -} - -void Airport::resume() { - this->m_pause = false; -} - -void Airport::resetData() { - std::lock_guard guard(this->m_lock); - this->m_flights.clear(); -} - -void Airport::updateFromEuroscope(types::Flight_t& flight) { - if (true == this->m_pause) - return; - - std::lock_guard guard(this->m_lock); - - bool found = false; - for (auto& pair : this->m_flights) { - if (flight.callsign == pair.first) { - const auto oldUpdateTime = pair.second[FlightEuroscope].lastUpdate; - pair.second[FlightEuroscope] = flight; - pair.second[FlightEuroscope].lastUpdate = oldUpdateTime; - found = true; - break; - } - } - - if (found == false) { - flight.lastUpdate = std::chrono::utc_clock::now(); - this->m_flights.insert({ flight.callsign, { flight, flight, types::Flight_t() } }); - } -} - -const std::string& Airport::airport() const { - return this->m_airport; -} - -void Airport::flightDisconnected(const std::string& callsign) { - if (true == this->m_pause) - return; - - std::lock_guard guard(this->m_lock); - - this->m_manualUpdatePerformance.start(); - auto it = this->m_flights.find(callsign); - if (it != this->m_flights.end() && it->second[FlightServer].callsign == callsign) { - Json::Value root; - - root["callsign"] = callsign; - root["inactive"] = true; - logging::Logger::instance().log("Airport", logging::Logger::Level::Debug, "Callsign disconnected: " + callsign); - Server::instance().patchFlight(callsign, root); - - it->second[FlightEuroscope].inactive = true; - } - this->m_manualUpdatePerformance.stop(); -} - -std::string Airport::timestampToIsoString(const std::chrono::utc_clock::time_point& timepoint) { - if (timepoint.time_since_epoch().count() >= 0) { - std::stringstream stream; - stream << std::format("{0:%FT%T}", timepoint); - auto timestamp = stream.str(); - timestamp = timestamp.substr(0, timestamp.length() - 4) + "Z"; - return timestamp; - } - else { - return "1969-12-31T23:59:59.999Z"; - } -} - -void Airport::updateExot(const std::string& callsign, const std::chrono::utc_clock::time_point& exot) { - if (true == this->m_pause) - return; - - this->m_manualUpdatePerformance.start(); - auto it = this->m_flights.find(callsign); - if (it != this->m_flights.end() && it->second[FlightServer].callsign == callsign) { - Json::Value root; - - root["callsign"] = callsign; - root["vacdm"] = Json::Value(); - root["vacdm"]["exot"] = std::chrono::duration_cast(exot.time_since_epoch()).count(); - root["vacdm"]["tsat"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["ttot"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["asat"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["aobt"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["atot"] = Airport::timestampToIsoString(types::defaultTime); - - it->second[FlightEuroscope].lastUpdate = std::chrono::utc_clock::now(); - it->second[FlightConsolidated].tsat = types::defaultTime; - it->second[FlightConsolidated].ttot = types::defaultTime; - it->second[FlightConsolidated].exot = types::defaultTime; - it->second[FlightConsolidated].asat = types::defaultTime; - it->second[FlightConsolidated].aobt = types::defaultTime; - it->second[FlightConsolidated].atot = types::defaultTime; - - logging::Logger::instance().log("Airport", logging::Logger::Level::Debug, "Updating EXOT: " + callsign + ", " + root["vacdm"]["exot"].asString()); - - std::lock_guard asyncGuard(this->m_asynchronousMessageLock); - this->m_asynchronousMessages.push_back({ - SendType::Patch, - callsign, - root, - }); - } - - this->m_manualUpdatePerformance.stop(); -} - -void Airport::updateTobt(const std::string& callsign, const std::chrono::utc_clock::time_point& tobt, bool manualTobt) { - if (true == this->m_pause) - return; - - this->m_manualUpdatePerformance.start(); - auto it = this->m_flights.find(callsign); - if (it != this->m_flights.end() && it->second[FlightServer].callsign == callsign) { - bool resetTsat = (tobt == types::defaultTime && true == manualTobt) || tobt >= it->second[FlightConsolidated].tsat; - Json::Value root; - - root["callsign"] = callsign; - root["vacdm"] = Json::Value(); - root["vacdm"]["tobt"] = Airport::timestampToIsoString(tobt); - if (true == resetTsat) - root["vacdm"]["tsat"] = Airport::timestampToIsoString(types::defaultTime); - if (false == manualTobt) { - root["vacdm"]["tobt_state"] = "CONFIRMED"; - } - - root["vacdm"]["ttot"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["asat"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["aobt"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["atot"] = Airport::timestampToIsoString(types::defaultTime); - - it->second[FlightEuroscope].lastUpdate = std::chrono::utc_clock::now(); - it->second[FlightConsolidated].tobt = tobt; - if (true == resetTsat) - it->second[FlightConsolidated].tsat = types::defaultTime; - it->second[FlightConsolidated].ttot = types::defaultTime; - it->second[FlightConsolidated].exot = types::defaultTime; - it->second[FlightConsolidated].asat = types::defaultTime; - it->second[FlightConsolidated].aobt = types::defaultTime; - it->second[FlightConsolidated].atot = types::defaultTime; - - logging::Logger::instance().log("Airport", logging::Logger::Level::Debug, "Updating TOBT: " + callsign + ", " + root["vacdm"]["tobt"].asString()); - - std::lock_guard asyncGuard(this->m_asynchronousMessageLock); - this->m_asynchronousMessages.push_back({ - SendType::Patch, - callsign, - root, - }); - } - this->m_manualUpdatePerformance.stop(); -} - -void Airport::resetTobt(const std::string& callsign, const std::chrono::utc_clock::time_point& tobt, const std::string& tobtState) { - if (true == this->m_pause) - return; - - this->m_manualUpdatePerformance.start(); - auto it = this->m_flights.find(callsign); - if (it != this->m_flights.end() && it->second[FlightServer].callsign == callsign) { - Json::Value root; - - root["callsign"] = callsign; - root["vacdm"] = Json::Value(); - - root["vacdm"]["tobt"] = Airport::timestampToIsoString(tobt); - root["vacdm"]["tobt_state"] = tobtState; - root["vacdm"]["tsat"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["ttot"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["asat"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["asrt"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["aobt"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["aort"] = Airport::timestampToIsoString(types::defaultTime); - root["vacdm"]["atot"] = Airport::timestampToIsoString(types::defaultTime); - - it->second[FlightEuroscope].lastUpdate = std::chrono::utc_clock::now(); - it->second[FlightConsolidated].tobt = types::defaultTime; - it->second[FlightConsolidated].tsat = types::defaultTime; - it->second[FlightConsolidated].ttot = types::defaultTime; - it->second[FlightConsolidated].exot = types::defaultTime; - it->second[FlightConsolidated].asat = types::defaultTime; - it->second[FlightConsolidated].asrt = types::defaultTime; - it->second[FlightConsolidated].aobt = types::defaultTime; - it->second[FlightConsolidated].aort = types::defaultTime; - it->second[FlightConsolidated].atot = types::defaultTime; - - logging::Logger::instance().log("Airport", logging::Logger::Level::Debug, "Resetting TOBT: " + callsign + ", " + root["vacdm"]["tobt"].asString()); - - std::lock_guard asyncGuard(this->m_asynchronousMessageLock); - this->m_asynchronousMessages.push_back({ - SendType::Patch, - callsign, - root, - }); - } - this->m_manualUpdatePerformance.stop(); -} - -void Airport::updateAsat(const std::string& callsign, const std::chrono::utc_clock::time_point& asat) { - if (true == this->m_pause) - return; - - this->m_manualUpdatePerformance.start(); - auto it = this->m_flights.find(callsign); - if (it != this->m_flights.end() && it->second[FlightServer].callsign == callsign) { - Json::Value root; - - root["callsign"] = callsign; - root["vacdm"] = Json::Value(); - root["vacdm"]["asat"] = Airport::timestampToIsoString(asat); - - it->second[FlightEuroscope].lastUpdate = std::chrono::utc_clock::now(); - it->second[FlightConsolidated].asat = asat; - - logging::Logger::instance().log("Airport", logging::Logger::Level::Debug, "Updating ASAT: " + callsign + ", " + root["vacdm"]["asat"].asString()); - Server::instance().patchFlight(callsign, root); - } - this->m_manualUpdatePerformance.stop(); -} - -void Airport::updateAobt(const std::string& callsign, const std::chrono::utc_clock::time_point& aobt) { - if (true == this->m_pause) - return; - - std::lock_guard guard(this->m_lock); - - this->m_manualUpdatePerformance.start(); - auto it = this->m_flights.find(callsign); - if (it != this->m_flights.end() && it->second[FlightServer].callsign == callsign) { - Json::Value root; - - root["callsign"] = callsign; - root["vacdm"] = Json::Value(); - root["vacdm"]["aobt"] = Airport::timestampToIsoString(aobt); - - it->second[FlightEuroscope].lastUpdate = std::chrono::utc_clock::now(); - it->second[FlightConsolidated].aobt = aobt; - - logging::Logger::instance().log("Airport", logging::Logger::Level::Debug, "Updating AOBT: " + callsign + ", " + root["vacdm"]["aobt"].asString()); - - std::lock_guard asyncGuard(this->m_asynchronousMessageLock); - this->m_asynchronousMessages.push_back({ - SendType::Patch, - callsign, - root, - }); - } - this->m_manualUpdatePerformance.stop(); -} - -void Airport::updateAtot(const std::string& callsign, const std::chrono::utc_clock::time_point& atot) { - if (true == this->m_pause) - return; - - this->m_manualUpdatePerformance.start(); - auto it = this->m_flights.find(callsign); - if (it != this->m_flights.end() && it->second[FlightServer].callsign == callsign) { - Json::Value root; - - root["callsign"] = callsign; - root["vacdm"] = Json::Value(); - root["vacdm"]["atot"] = Airport::timestampToIsoString(atot); - - it->second[FlightEuroscope].lastUpdate = std::chrono::utc_clock::now(); - it->second[FlightConsolidated].atot = atot; - - logging::Logger::instance().log("Airport", logging::Logger::Level::Debug, "Updating ATOT: " + callsign + ", " + root["vacdm"]["atot"].asString()); - - std::lock_guard asyncGuard(this->m_asynchronousMessageLock); - this->m_asynchronousMessages.push_back({ - SendType::Patch, - callsign, - root, - }); - } - this->m_manualUpdatePerformance.stop(); -} - -void Airport::updateAsrt(const std::string& callsign, const std::chrono::utc_clock::time_point& asrt) { - if (true == this->m_pause) - return; - - this->m_manualUpdatePerformance.start(); - auto it = this->m_flights.find(callsign); - if (it != this->m_flights.end() && it->second[FlightServer].callsign == callsign) { - Json::Value root; - - root["callsign"] = callsign; - root["vacdm"] = Json::Value(); - root["vacdm"]["asrt"] = Airport::timestampToIsoString(asrt); - - it->second[FlightEuroscope].lastUpdate = std::chrono::utc_clock::now(); - it->second[FlightConsolidated].asrt = asrt; - - logging::Logger::instance().log("Airport", logging::Logger::Level::Debug, "Updating ASRT: " + callsign + ", " + root["vacdm"]["asrt"].asString()); - - std::lock_guard asyncGuard(this->m_asynchronousMessageLock); - this->m_asynchronousMessages.push_back({ - SendType::Patch, - callsign, - root, - }); - } - this->m_manualUpdatePerformance.stop(); -} - -void Airport::updateAort(const std::string& callsign, const std::chrono::utc_clock::time_point& aort) { - if (true == this->m_pause) - return; - - this->m_manualUpdatePerformance.start(); - auto it = this->m_flights.find(callsign); - if (it != this->m_flights.end() && it->second[FlightServer].callsign == callsign) { - Json::Value root; - - root["callsign"] = callsign; - root["vacdm"] = Json::Value(); - root["vacdm"]["aort"] = Airport::timestampToIsoString(aort); - - it->second[FlightEuroscope].lastUpdate = std::chrono::utc_clock::now(); - it->second[FlightConsolidated].aort = aort; - - logging::Logger::instance().log("Airport", logging::Logger::Level::Debug, "Updating AORT: " + callsign + ", " + root["vacdm"]["aort"].asString()); - - std::lock_guard asyncGuard(this->m_asynchronousMessageLock); - this->m_asynchronousMessages.push_back({ - SendType::Patch, - callsign, - root, - }); - } - this->m_manualUpdatePerformance.stop(); -} - -void Airport::deleteFlight(const std::string& callsign) { - if (true == this->m_pause) - return; - - this->m_manualUpdatePerformance.start(); - auto it = this->m_flights.find(callsign); - if (it != this->m_flights.end() && it->second[FlightServer].callsign == callsign) { - std::lock_guard asnycGuard(this->m_asynchronousMessageLock); - this->m_asynchronousMessages.push_back({ - SendType::Delete, - callsign, - }); - this->m_lock.lock(); - it = this->m_flights.erase(it); - this->m_lock.unlock(); - } - this->m_manualUpdatePerformance.stop(); - logging::Logger::instance().log("Airport", logging::Logger::Level::Debug, "Added async message to delete: " + callsign); -} - -Airport::SendType Airport::deltaEuroscopeToBackend(const std::array& data, Json::Value& root) { - root.clear(); - - root["callsign"] = data[FlightEuroscope].callsign; - - if (data[FlightServer].callsign == "" && data[FlightEuroscope].callsign != "") { - root["inactive"] = false; - - root["position"] = Json::Value(); - root["position"]["lat"] = data[FlightEuroscope].latitude; - root["position"]["lon"] = data[FlightEuroscope].longitude; - - root["flightplan"] = Json::Value(); - root["flightplan"]["flight_rules"] = data[FlightEuroscope].rule; - root["flightplan"]["departure"] = data[FlightEuroscope].origin; - root["flightplan"]["arrival"] = data[FlightEuroscope].destination; - - root["vacdm"] = Json::Value(); - root["vacdm"]["eobt"] = Airport::timestampToIsoString(data[FlightEuroscope].eobt); - root["vacdm"]["tobt"] = Airport::timestampToIsoString(data[FlightEuroscope].tobt); - - root["clearance"] = Json::Value(); - root["clearance"]["dep_rwy"] = data[FlightEuroscope].runway; - root["clearance"]["sid"] = data[FlightEuroscope].sid; - root["clearance"]["initial_climb"] = data[FlightEuroscope].initialClimb; - root["clearance"]["assigned_squawk"] = data[FlightEuroscope].assignedSquawk; - root["clearance"]["current_squawk"] = data[FlightEuroscope].currentSquawk; - - return Airport::SendType::Post; - } - else { - int deltaCount = 0; - - if (data[FlightEuroscope].inactive != data[FlightServer].inactive) { - root["inactive"] = data[FlightEuroscope].inactive; - deltaCount += 1; - } - - auto lastDelta = deltaCount; - root["position"] = Json::Value(); - if (data[FlightEuroscope].latitude != data[FlightServer].latitude) { - root["position"]["lat"] = data[FlightEuroscope].latitude; - deltaCount += 1; - } - if (data[FlightEuroscope].longitude != data[FlightServer].longitude) { - root["position"]["lon"] = data[FlightEuroscope].longitude; - deltaCount += 1; - } - if (deltaCount == lastDelta) - root.removeMember("position"); - - lastDelta = deltaCount; - root["flightplan"] = Json::Value(); - if (data[FlightEuroscope].origin != data[FlightServer].origin) { - root["flightplan"]["departure"] = data[FlightEuroscope].origin; - deltaCount += 1; - } - if (data[FlightEuroscope].destination != data[FlightServer].destination) { - root["flightplan"]["arrival"] = data[FlightEuroscope].destination; - deltaCount += 1; - } - if (lastDelta == deltaCount) - root.removeMember("flightplan"); - - lastDelta = deltaCount; - root["clearance"] = Json::Value(); - if (data[FlightEuroscope].runway != data[FlightServer].runway) { - root["clearance"]["dep_rwy"] = data[FlightEuroscope].runway; - deltaCount += 1; - } - if (data[FlightEuroscope].sid != data[FlightServer].sid) { - root["clearance"]["sid"] = data[FlightEuroscope].sid; - deltaCount += 1; - } - if (data[FlightEuroscope].initialClimb != data[FlightServer].initialClimb) { - root["clearance"]["initial_climb"] = data[FlightEuroscope].initialClimb; - deltaCount += 1; - } - if (data[FlightEuroscope].assignedSquawk != data[FlightServer].assignedSquawk) { - root["clearance"]["assigned_squawk"] = data[FlightEuroscope].assignedSquawk; - deltaCount += 1; - } - if (data[FlightEuroscope].currentSquawk != data[FlightServer].currentSquawk) { - root["clearance"]["current_squawk"] = data[FlightEuroscope].currentSquawk; - deltaCount += 1; - } - if (lastDelta == deltaCount) - root.removeMember("clearance"); - - return deltaCount != 0 ? Airport::SendType::Patch : Airport::SendType::None; - } -} - -void Airport::consolidateData(std::array& data) { - if (data[FlightEuroscope].callsign == data[FlightServer].callsign) { - data[FlightConsolidated].inactive = data[FlightServer].inactive; - - data[FlightConsolidated].latitude = data[FlightEuroscope].latitude; - data[FlightConsolidated].longitude = data[FlightEuroscope].longitude; - - data[FlightConsolidated].origin = data[FlightEuroscope].origin; - data[FlightConsolidated].destination = data[FlightEuroscope].destination; - data[FlightConsolidated].rule = data[FlightEuroscope].rule; - - data[FlightConsolidated].lastUpdate = data[FlightServer].lastUpdate; - data[FlightConsolidated].eobt = data[FlightServer].eobt; - data[FlightConsolidated].tobt = data[FlightServer].tobt; - data[FlightConsolidated].ctot = data[FlightServer].ctot; - data[FlightConsolidated].ttot = data[FlightServer].ttot; - data[FlightConsolidated].tsat = data[FlightServer].tsat; - data[FlightConsolidated].exot = data[FlightServer].exot; - data[FlightConsolidated].asat = data[FlightServer].asat; - data[FlightConsolidated].aobt = data[FlightServer].aobt; - data[FlightConsolidated].atot = data[FlightServer].atot; - data[FlightConsolidated].aort = data[FlightServer].aort; - data[FlightConsolidated].asrt = data[FlightServer].asrt; - data[FlightConsolidated].tobt_state = data[FlightServer].tobt_state; - - data[FlightConsolidated].measures = data[FlightServer].measures; - - data[FlightConsolidated].hasBooking = data[FlightServer].hasBooking; - - data[FlightConsolidated].runway = data[FlightEuroscope].runway; - data[FlightConsolidated].sid = data[FlightEuroscope].sid; - data[FlightConsolidated].assignedSquawk = data[FlightEuroscope].assignedSquawk; - data[FlightConsolidated].currentSquawk = data[FlightEuroscope].currentSquawk; - data[FlightConsolidated].initialClimb = data[FlightEuroscope].initialClimb; - - data[FlightConsolidated].departed = data[FlightEuroscope].departed; - logging::Logger::instance().log("vACDM", logging::Logger::Level::Debug, "Consolidated " + data[FlightServer].callsign); - } - else { - logging::Logger::instance().log("vACDM", logging::Logger::Level::Debug, - "Invalid callsign match in consolidation: " + data[FlightEuroscope].callsign + ", " + data[FlightServer].callsign); - } -} - -void Airport::processAsynchronousMessages() { - // get a snapshot of the message count to avoid endless loops during long processing times - this->m_asynchronousMessageLock.lock(); - std::size_t messageCount = this->m_asynchronousMessages.size(); - this->m_asynchronousMessageLock.unlock(); - - for (std::size_t i = 0; i < messageCount; ++i) { - // get the first message - this->m_asynchronousMessageLock.lock(); - // double check for empty queues - if (this->m_asynchronousMessages.size() == 0) { - this->m_asynchronousMessageLock.unlock(); - break; - } - - AsynchronousMessage message = this->m_asynchronousMessages.front(); - this->m_asynchronousMessages.pop_front(); - this->m_asynchronousMessageLock.unlock(); - - if (message.type == SendType::Patch) { - Server::instance().patchFlight(message.callsign, message.content); - } - else if (message.type == SendType::Post) { - Server::instance().postFlight(message.content); - } - else if (message.type == SendType::Delete) { - Server::instance().deleteFlight(message.callsign); - } - } -} - -void Airport::run() { - std::size_t counter = 1; - while (true) { - std::this_thread::sleep_for(1s); - if (true == this->m_stop) - return; - // run every five seconds - if (counter++ % 5 != 0) - continue; - // pause until master resumed - if (true == this->m_pause) - continue; - - // process the backlog queue of asynchronous messages - this->processAsynchronousMessages(); - - // get the newest updates - this->m_workerAllFlightsPerformance.start(); - logging::Logger::instance().log("Airport", logging::Logger::Level::Debug, "New server update cycle"); - auto flights = com::Server::instance().allFlights(this->m_airport); - std::list> transmissionBuffer; - this->m_workerAllFlightsPerformance.stop(); - - this->m_lock.lock(); - // check which updates are needed and update consolidated views based on the server - for (auto it = this->m_flights.begin(); this->m_flights.end() != it;) { - if (it->second[FlightEuroscope].callsign.length() == 0 || true == it->second[FlightConsolidated].departed) { - logging::Logger::instance().log("vACDM", logging::Logger::Level::Debug, - "Skipping flight\nCallsigns: " + - it->first + ", " + it->second[FlightEuroscope].callsign + "\nDeparted: " + - std::to_string(it->second[FlightConsolidated].departed) - ); - ++it; - continue; - } - - if (false == it->second[FlightEuroscope].departed && it->second[FlightConsolidated].atot.time_since_epoch().count() > 0) { - logging::Logger::instance().log("vACDM", logging::Logger::Level::Debug, - "ATOT of " + it->first + " indicates departure: " + - std::to_string(it->second[FlightConsolidated].atot.time_since_epoch().count()) - ); - it->second[FlightEuroscope].departed = true; - } - - bool removeFlight = it->second[FlightServer].inactive == true; - for (auto rit = flights.begin(); flights.end() != rit; ++rit) { - if (rit->callsign == it->second[FlightConsolidated].callsign) { - it->second[FlightServer] = *rit; - Airport::consolidateData(it->second); - removeFlight = false; - break; - } - } - - Json::Value root; - const auto sendType = Airport::deltaEuroscopeToBackend(it->second, root); - if (Airport::SendType::None != sendType) - transmissionBuffer.push_back({ it->first, sendType, root }); - - if (true == removeFlight) { - logging::Logger::instance().log("vACDM", logging::Logger::Level::Debug, "Deleting " + it->first); - it = this->m_flights.erase(it); - } - else { - ++it; - } - } - - this->m_lock.unlock(); - - // send the deltas - this->m_workerUpdateFlightsPerformance.start(); - for (const auto& transmission : std::as_const(transmissionBuffer)) { - if (std::get<1>(transmission) == Airport::SendType::Post) - Server::instance().postFlight(std::get<2>(transmission)); - else - Server::instance().patchFlight(std::get<0>(transmission), std::get<2>(transmission)); - } - this->m_workerUpdateFlightsPerformance.stop(); - } -} - -bool Airport::flightExists(const std::string& callsign) { - if (true == this->m_pause) - return false; - - std::lock_guard guard(this->m_lock); - return this->m_flights.cend() != this->m_flights.find(callsign); -} - -const types::Flight_t& Airport::flight(const std::string& callsign) { - std::lock_guard guard(this->m_lock); - return this->m_flights.find(callsign)->second[FlightConsolidated]; -} diff --git a/com/Airport.h b/com/Airport.h deleted file mode 100644 index cfb5ab3..0000000 --- a/com/Airport.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include "PerformanceLock.h" - -namespace vacdm { -namespace com { - -class Airport { -private: - enum class SendType { - Post, - Patch, - Delete, - None - }; - - struct AsynchronousMessage { - SendType type; - std::string callsign; - Json::Value content; - }; - - std::string m_airport; - std::thread m_worker; - volatile bool m_pause; - PerformanceLock m_lock; - std::map> m_flights; - volatile bool m_stop; - logging::Performance m_manualUpdatePerformance; - logging::Performance m_workerAllFlightsPerformance; - logging::Performance m_workerUpdateFlightsPerformance; - PerformanceLock m_asynchronousMessageLock; - std::list m_asynchronousMessages; - - void processAsynchronousMessages(); - static std::string timestampToIsoString(const std::chrono::utc_clock::time_point& timepoint); - static SendType deltaEuroscopeToBackend(const std::array& data, Json::Value& root); - static void consolidateData(std::array& data); - void run(); - -public: - Airport(); - Airport(const std::string& airport); - ~Airport(); - - void pause(); - void resume(); - void resetData(); - const std::string& airport() const; - void updateFromEuroscope(types::Flight_t& flight); - void flightDisconnected(const std::string& callsign); - void updateExot(const std::string& callsign, const std::chrono::utc_clock::time_point& exot); - void updateTobt(const std::string& callsign, const std::chrono::utc_clock::time_point& tobt, bool manualTobt); - void resetTobt(const std::string& callsign, const std::chrono::utc_clock::time_point& tobt, const std::string& tobtState); - void updateAsat(const std::string& callsign, const std::chrono::utc_clock::time_point& asat); - void updateAobt(const std::string& callsign, const std::chrono::utc_clock::time_point& aobt); - void updateAtot(const std::string& callsign, const std::chrono::utc_clock::time_point& atot); - void updateAsrt(const std::string& callsign, const std::chrono::utc_clock::time_point& asrt); - void updateAort(const std::string& callsign, const std::chrono::utc_clock::time_point& aort); - void deleteFlight(const std::string& callsign); - bool flightExists(const std::string& callsign); - const types::Flight_t& flight(const std::string& callsign); -}; - -} -} diff --git a/com/PerformanceLock.h b/com/PerformanceLock.h deleted file mode 100644 index 28bb84c..0000000 --- a/com/PerformanceLock.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include - -#include - -namespace vacdm { -namespace com { - class PerformanceLock { - private: - vacdm::logging::Performance m_acquireTime; - vacdm::logging::Performance m_criticalSectionTime; - - std::mutex m_lock; - public: - PerformanceLock(const std::string& component) : - m_acquireTime("LockAcquire" + component), - m_criticalSectionTime("CriticalSectionAcquire" + component), - m_lock() {} - - void lock() { - this->m_acquireTime.start(); - this->m_lock.lock(); - this->m_acquireTime.stop(); - - this->m_criticalSectionTime.start(); - } - - void unlock() { - this->m_lock.unlock(); - this->m_criticalSectionTime.stop(); - } - }; -} -} diff --git a/com/Server.cpp b/com/Server.cpp deleted file mode 100644 index d17dbc1..0000000 --- a/com/Server.cpp +++ /dev/null @@ -1,379 +0,0 @@ -#include "Server.h" -#include "Version.h" -#include "logging/Logger.h" - -using namespace vacdm; -using namespace vacdm::com; - -static std::string __receivedDeleteData; -static std::string __receivedGetData; -static std::string __receivedPatchData; -static std::string __receivedPostData; - -static std::size_t receiveCurlDelete(void* ptr, std::size_t size, std::size_t nmemb, void* stream) { - (void)stream; - - std::string serverResult = static_cast(ptr); - __receivedDeleteData += serverResult; - return size * nmemb; -} - -static std::size_t receiveCurlGet(void* ptr, std::size_t size, std::size_t nmemb, void* stream) { - (void)stream; - - std::string serverResult = static_cast(ptr); - __receivedGetData += serverResult; - return size * nmemb; -} - -static std::size_t receiveCurlPatch(void* ptr, std::size_t size, std::size_t nmemb, void* stream) { - (void)stream; - - std::string serverResult = static_cast(ptr); - __receivedPatchData += serverResult; - return size * nmemb; -} - -static std::size_t receiveCurlPost(void* ptr, std::size_t size, std::size_t nmemb, void* stream) { - (void)stream; - - std::string serverResult = static_cast(ptr); - __receivedPostData += serverResult; - return size * nmemb; -} - -Server::Server() : - m_authToken(), - m_getRequest(), - m_postRequest(), - m_patchRequest(), - m_deleteRequest(), - m_firstCall(true), - m_validWebApi(false), - m_baseUrl("https://vacdm.vatsim-germany.org"), - m_master(false), - m_errorCode() { - /* configure the get request */ - curl_easy_setopt(m_getRequest.socket, CURLOPT_SSL_VERIFYPEER, 0L); - curl_easy_setopt(m_getRequest.socket, CURLOPT_SSL_VERIFYHOST, 0L); - curl_easy_setopt(m_getRequest.socket, CURLOPT_HTTP_VERSION, static_cast(CURL_HTTP_VERSION_1_1)); - curl_easy_setopt(m_getRequest.socket, CURLOPT_HTTPGET, 1L); - curl_easy_setopt(m_getRequest.socket, CURLOPT_WRITEFUNCTION, receiveCurlGet); - curl_easy_setopt(m_getRequest.socket, CURLOPT_TIMEOUT, 2L); - - /* configure the post request */ - curl_easy_setopt(m_postRequest.socket, CURLOPT_SSL_VERIFYPEER, 0L); - curl_easy_setopt(m_postRequest.socket, CURLOPT_SSL_VERIFYHOST, 0L); - curl_easy_setopt(m_postRequest.socket, CURLOPT_HTTP_VERSION, static_cast(CURL_HTTP_VERSION_1_1)); - curl_easy_setopt(m_postRequest.socket, CURLOPT_WRITEFUNCTION, receiveCurlPost); - curl_easy_setopt(m_postRequest.socket, CURLOPT_CUSTOMREQUEST, "POST"); - curl_easy_setopt(m_postRequest.socket, CURLOPT_VERBOSE, 1); - struct curl_slist* headers = nullptr; - headers = curl_slist_append(headers, "Accept: application/json"); - headers = curl_slist_append(headers, ("Authorization: Bearer " + this->m_authToken).c_str()); - headers = curl_slist_append(headers, "Content-Type: application/json"); - curl_easy_setopt(m_postRequest.socket, CURLOPT_HTTPHEADER, headers); - - /* configure the patch request */ - curl_easy_setopt(m_patchRequest.socket, CURLOPT_SSL_VERIFYPEER, 0L); - curl_easy_setopt(m_patchRequest.socket, CURLOPT_SSL_VERIFYHOST, 0L); - curl_easy_setopt(m_patchRequest.socket, CURLOPT_HTTP_VERSION, static_cast(CURL_HTTP_VERSION_1_1)); - curl_easy_setopt(m_patchRequest.socket, CURLOPT_WRITEFUNCTION, receiveCurlPatch); - curl_easy_setopt(m_patchRequest.socket, CURLOPT_CUSTOMREQUEST, "PATCH"); - curl_easy_setopt(m_patchRequest.socket, CURLOPT_VERBOSE, 1); - curl_easy_setopt(m_patchRequest.socket, CURLOPT_HTTPHEADER, headers); - - /* configure the delete request */ - curl_easy_setopt(m_deleteRequest.socket, CURLOPT_SSL_VERIFYPEER, 0L); - curl_easy_setopt(m_deleteRequest.socket, CURLOPT_SSL_VERIFYHOST, 0L); - curl_easy_setopt(m_deleteRequest.socket, CURLOPT_HTTP_VERSION, static_cast(CURL_HTTP_VERSION_1_1)); - curl_easy_setopt(m_deleteRequest.socket, CURLOPT_CUSTOMREQUEST, "DELETE"); - curl_easy_setopt(m_deleteRequest.socket, CURLOPT_WRITEFUNCTION, receiveCurlDelete); - curl_easy_setopt(m_deleteRequest.socket, CURLOPT_TIMEOUT, 2L); -} - -Server::~Server() { - if (nullptr != m_getRequest.socket) { - std::lock_guard guard(m_getRequest.lock); - curl_easy_cleanup(m_getRequest.socket); - m_getRequest.socket = nullptr; - } - - if (nullptr != m_postRequest.socket) { - std::lock_guard guard(m_postRequest.lock); - curl_easy_cleanup(m_postRequest.socket); - m_postRequest.socket = nullptr; - } - - if (nullptr != m_patchRequest.socket) { - std::lock_guard guard(m_patchRequest.lock); - curl_easy_cleanup(m_patchRequest.socket); - m_patchRequest.socket = nullptr; - } - - if (nullptr != m_deleteRequest.socket) { - std::lock_guard guard(m_deleteRequest.lock); - curl_easy_cleanup(m_deleteRequest.socket); - m_deleteRequest.socket = nullptr; - } -} - -void Server::changeServerAddress(const std::string& url) { - this->m_baseUrl = url; - this->m_validWebApi = false; - this->m_firstCall = true; -} - -bool Server::checkWepApi() { - /* API is already checked */ - if (false == m_firstCall) - return this->m_validWebApi; - - m_validWebApi = false; - - std::lock_guard guard(m_getRequest.lock); - if (nullptr != m_getRequest.socket && true == m_firstCall) { - __receivedGetData.clear(); - - std::string url = m_baseUrl + "/api/v1/version"; - curl_easy_setopt(m_getRequest.socket, CURLOPT_URL, url.c_str()); - - /* send the command */ - CURLcode result = curl_easy_perform(m_getRequest.socket); - if (CURLE_OK == result) { - Json::CharReaderBuilder builder{}; - auto reader = std::unique_ptr(builder.newCharReader()); - std::string errors; - Json::Value root; - - logging::Logger::instance().log("Server", logging::Logger::Level::System, "Received API-version-message: " + __receivedGetData); - if (reader->parse(__receivedGetData.c_str(), __receivedGetData.c_str() + __receivedGetData.length(), &root, &errors)) { - if (PLUGIN_VERSION_MAJOR != root.get("major", Json::Value(-1)).asInt()) { - this->m_errorCode = "Backend-version is incompatible. Please update the plugin."; - this->m_validWebApi = false; - } - else { - this->m_validWebApi = true; - } - } - else { - this->m_errorCode = "Invalid backend-version response: " + __receivedGetData; - this->m_validWebApi = false; - } - } - else { - this->m_errorCode = "Connection to the server broken"; - } - } - - m_firstCall = false; - return this->m_validWebApi; -} - -Server::ServerConfiguration_t Server::serverConfiguration() { - /* API is already checked */ - if (true == this->m_firstCall || false == this->m_validWebApi) - return Server::ServerConfiguration_t(); - - std::lock_guard guard(m_getRequest.lock); - if (nullptr != m_getRequest.socket) { - __receivedGetData.clear(); - - std::string url = m_baseUrl + "/api/v1/config"; - curl_easy_setopt(m_getRequest.socket, CURLOPT_URL, url.c_str()); - - /* send the command */ - CURLcode result = curl_easy_perform(m_getRequest.socket); - if (CURLE_OK == result) { - Json::CharReaderBuilder builder{}; - auto reader = std::unique_ptr(builder.newCharReader()); - std::string errors; - Json::Value root; - - logging::Logger::instance().log("Server", logging::Logger::Level::System, "Received configuration: " + __receivedGetData); - if (reader->parse(__receivedGetData.c_str(), __receivedGetData.c_str() + __receivedGetData.length(), &root, &errors)) { - ServerConfiguration_t config; - config.name = root["serverName"].asString(); - config.masterInSweatbox = root["allowSimSession"].asBool(); - config.masterAsObserver = root["allowObsMaster"].asBool(); - return config; - } - } - } - - return ServerConfiguration_t(); -} - -void Server::setMaster(bool master) { - this->m_master = master; -} - -std::chrono::utc_clock::time_point Server::isoStringToTimestamp(const std::string& timestamp) { - std::chrono::utc_clock::time_point retval; - std::stringstream stream; - - stream << timestamp.substr(0, timestamp.length() - 1); - std::chrono::from_stream(stream, "%FT%T", retval); - - return retval; -} - -std::list Server::allFlights(const std::string& airport) { - if (true == this->m_firstCall || false == this->m_validWebApi) - return {}; - - std::lock_guard guard(m_getRequest.lock); - if (nullptr != m_getRequest.socket) { - __receivedGetData.clear(); - - std::string url = m_baseUrl + "/api/v1/pilots"; - if (airport.length() != 0) - url += "?adep=" + airport; - curl_easy_setopt(m_getRequest.socket, CURLOPT_URL, url.c_str()); - - /* send the command */ - CURLcode result = curl_easy_perform(m_getRequest.socket); - if (CURLE_OK == result) { - Json::CharReaderBuilder builder{}; - auto reader = std::unique_ptr(builder.newCharReader()); - std::string errors; - Json::Value root; - - logging::Logger::instance().log("Server", logging::Logger::Level::Debug, "Airport update: " + url); - logging::Logger::instance().log("Server", logging::Logger::Level::Debug, __receivedGetData); - if (reader->parse(__receivedGetData.c_str(), __receivedGetData.c_str() + __receivedGetData.length(), &root, &errors) && root.isArray()) { - std::list flights; - - for (const auto& flight : std::as_const(root)) { - flights.push_back(types::Flight_t()); - - flights.back().lastUpdate = Server::isoStringToTimestamp(flight["updatedAt"].asString()); - flights.back().callsign = flight["callsign"].asString(); - flights.back().inactive = flight["inactive"].asBool(); - - flights.back().latitude = flight["position"]["lat"].asDouble(); - flights.back().longitude = flight["position"]["lon"].asDouble(); - - flights.back().origin = flight["flightplan"]["departure"].asString(); - flights.back().destination = flight["flightplan"]["arrival"].asString(); - flights.back().rule = flight["flightplan"]["flight_rules"].asString(); - - flights.back().eobt = Server::isoStringToTimestamp(flight["vacdm"]["eobt"].asString()); - flights.back().tobt = Server::isoStringToTimestamp(flight["vacdm"]["tobt"].asString()); - flights.back().ctot = Server::isoStringToTimestamp(flight["vacdm"]["ctot"].asString()); - flights.back().ttot = Server::isoStringToTimestamp(flight["vacdm"]["ttot"].asString()); - flights.back().tsat = Server::isoStringToTimestamp(flight["vacdm"]["tsat"].asString()); - flights.back().asat = Server::isoStringToTimestamp(flight["vacdm"]["asat"].asString()); - flights.back().aobt = Server::isoStringToTimestamp(flight["vacdm"]["aobt"].asString()); - flights.back().atot = Server::isoStringToTimestamp(flight["vacdm"]["atot"].asString()); - flights.back().exot = std::chrono::utc_clock::time_point(std::chrono::minutes(flight["vacdm"]["exot"].asInt64())); - flights.back().asrt = Server::isoStringToTimestamp(flight["vacdm"]["asrt"].asString()); - flights.back().aort = Server::isoStringToTimestamp(flight["vacdm"]["aort"].asString()); - flights.back().ctot = Server::isoStringToTimestamp(flight["vacdm"]["ctot"].asString()); - flights.back().tobt_state = flight["vacdm"]["tobt_state"].asString(); - - // ecfmp - Json::Value measuresArray = flight["measures"]; - std::vector parsedMeasures; - if (!measuresArray.empty()) { - for (size_t i = 0; i < measuresArray.size(); i++) { - vacdm::types::Measure measure; - // Extract the ident and value fields from the JSON object - measure.ident = measuresArray[i]["ident"].asString(); - measure.value = measuresArray[i]["value"].asInt(); - - // Add the measure struct to the vector - parsedMeasures.push_back(measure); - - logging::Logger::instance().log("JSON", logging::Logger::Level::Debug, "Values: " + measure.ident + std::to_string(measure.value)); - } - if (!parsedMeasures.empty()) { - flights.back().measures = parsedMeasures; - } - } - else { - logging::Logger::instance().log("JSON", logging::Logger::Level::Debug, "Values: empty"); - } - - flights.back().hasBooking = flight["hasBooking"].asBool(); - - flights.back().runway = flight["clearance"]["dep_rwy"].asString(); - flights.back().sid = flight["clearance"]["sid"].asString(); - flights.back().assignedSquawk = flight["clearance"]["assigned_squawk"].asString(); - flights.back().currentSquawk = flight["clearance"]["current_squawk"].asString(); - flights.back().initialClimb = flight["clearance"]["initial_climb"].asString(); - } - - return flights; - } - } - } - - return {}; -} - -void Server::postFlight(const Json::Value& root) { - if (true == this->m_firstCall || false == this->m_validWebApi || false == this->m_master) - return; - - Json::StreamWriterBuilder builder{}; - const auto message = Json::writeString(builder, root); - logging::Logger::instance().log("Server", logging::Logger::Level::Debug, "POST: " + message); - - std::lock_guard guard(this->m_postRequest.lock); - if (nullptr != m_postRequest.socket) { - std::string url = m_baseUrl + "/api/v1/pilots"; - curl_easy_setopt(m_postRequest.socket, CURLOPT_URL, url.c_str()); - curl_easy_setopt(m_postRequest.socket, CURLOPT_POSTFIELDS, message.c_str()); - - /* send the command */ - curl_easy_perform(m_postRequest.socket); - } -} - -void Server::patchFlight(const std::string& callsign, const Json::Value& root) { - if (true == this->m_firstCall || false == this->m_validWebApi || false == this->m_master) - return; - - Json::StreamWriterBuilder builder{}; - const auto message = Json::writeString(builder, root); - logging::Logger::instance().log("Server", logging::Logger::Level::Debug, "PATCH: " + message); - - std::lock_guard guard(this->m_patchRequest.lock); - if (nullptr != m_patchRequest.socket) { - std::string url = m_baseUrl + "/api/v1/pilots/" + callsign; - curl_easy_setopt(m_patchRequest.socket, CURLOPT_URL, url.c_str()); - curl_easy_setopt(m_patchRequest.socket, CURLOPT_POSTFIELDS, message.c_str()); - - /* send the command */ - curl_easy_perform(m_patchRequest.socket); - } -} - -void Server::deleteFlight(const std::string& callsign) { - if (true == this->m_firstCall || false == this->m_validWebApi || false == this->m_master) - return; - - Json::StreamWriterBuilder builder{}; - - logging::Logger::instance().log("Server", logging::Logger::Level::Debug, "DELETE: " + callsign); - - std::lock_guard guard(this->m_deleteRequest.lock); - if (m_deleteRequest.socket != nullptr) { - std::string url = m_baseUrl + "/api/v1/pilots/" + callsign; - - curl_easy_setopt(m_deleteRequest.socket, CURLOPT_URL, url.c_str()); - - /* send the command */ - curl_easy_perform(m_deleteRequest.socket); - } -} - -const std::string& Server::errorMessage() const { - return this->m_errorCode; -} - -Server& Server::instance() { - static Server __instance; - return __instance; -} diff --git a/com/Server.h b/com/Server.h deleted file mode 100644 index fba70ef..0000000 --- a/com/Server.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include -#include -#include - -#define CURL_STATICLIB 1 -#include -#include - -#include - -namespace vacdm { -namespace com { - -/** - * @brief Defines the communication with the vACDM backend - */ -class Server { -public: - typedef struct ServerConfiguration { - std::string name = ""; - bool masterInSweatbox = false; - bool masterAsObserver = false; - } ServerConfiguration_t; - -private: - struct Communication - { - std::mutex lock; - CURL* socket; - - Communication() : lock(), socket(curl_easy_init()) { } - }; - - std::string m_authToken; - Communication m_getRequest; - Communication m_postRequest; - Communication m_patchRequest; - Communication m_deleteRequest; - bool m_firstCall; - bool m_validWebApi; - std::string m_baseUrl; - bool m_master; - std::string m_errorCode; - - Server(); - - static std::chrono::utc_clock::time_point isoStringToTimestamp(const std::string& timestamp); - -public: - ~Server(); - Server(const Server&) = delete; - Server(Server&&) = delete; - - Server& operator=(const Server&) = delete; - Server& operator=(Server&&) = delete; - - void changeServerAddress(const std::string& url); - bool checkWepApi(); - ServerConfiguration_t serverConfiguration(); - std::list allFlights(const std::string& airport = ""); - void postFlight(const Json::Value& root); - void patchFlight(const std::string& callsign, const Json::Value& root); - void deleteFlight(const std::string& callsign); - void setMaster(bool master); - const std::string& errorMessage() const; - - /** - * @brief Returns the current server instance - * @return The server instance - */ - static Server& instance(); -}; - -} -} diff --git a/config/FileFormat.cpp b/config/FileFormat.cpp deleted file mode 100644 index 2c18533..0000000 --- a/config/FileFormat.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include -#include -#include -#include - -#include - -#include "FileFormat.h" - -using namespace vacdm; - -FileFormat::FileFormat() noexcept : - m_errorLine(std::numeric_limits::max()), - m_errorMessage() { } - -void FileFormat::reset() noexcept { - this->m_errorLine = std::numeric_limits::max(); - this->m_errorMessage.clear(); -} - -bool FileFormat::errorFound() const noexcept { - return std::numeric_limits::max() != this->m_errorLine; -} - -std::uint32_t FileFormat::errorLine() const noexcept { - return this->m_errorLine; -} - -const std::string& FileFormat::errorMessage() const noexcept { - return this->m_errorMessage; -} - -bool FileFormat::parseColor(const std::string& block, COLORREF& color, std::uint32_t line) { - auto split = helper::String::splitString(block, ","); - if (3 != split.size()) { - this->m_errorLine = line; - this->m_errorMessage = "Invalid color configuration"; - return false; - } - - std::array data; - for (std::size_t i = 0; i < 3; ++i) - data[i] = static_cast(std::atoi(split[i].c_str())); - color = RGB(data[0], data[1], data[2]); - - return true; -} - -bool FileFormat::parse(const std::string& filename, SystemConfig& config) { - config.valid = false; - - std::ifstream stream(filename); - if (false == stream.is_open()) { - this->m_errorMessage = "Unable to open the configuration file"; - this->m_errorLine = 0; - return false; - } - - std::string line; - std::uint32_t lineOffset = 0; - while (std::getline(stream, line)) { - std::string value; - - lineOffset += 1; - - /* skip a new line */ - if (0 == line.length()) - continue; - - /* trimm the line and check if a comment line is used */ - std::string trimmed = helper::String::trim(line); - if (0 == trimmed.find_first_of('#', 0)) - continue; - - auto entry = helper::String::splitString(trimmed, "="); - if (2 > entry.size()) { - this->m_errorLine = lineOffset; - this->m_errorMessage = "Invalid configuration entry"; - config.valid = false; - return false; - } - else if (2 < entry.size()) { - for (std::size_t idx = 1; idx < entry.size() - 1; ++idx) - value += entry[idx] + "="; - value += entry.back(); - } - else { - value = entry[1]; - } - - /* found an invalid line */ - if (0 == value.length()) { - this->m_errorLine = lineOffset; - this->m_errorMessage = "Invalid entry"; - return false; - } - - bool parsed = false; - if ("SERVER_url" == entry[0]) { - config.serverUrl = entry[1]; - parsed = true; - } else if ("COLOR_lightgreen" == entry[0]) { - parsed = this->parseColor(entry[1], config.lightgreen, lineOffset); - } else if ("COLOR_lightblue" == entry[0]) { - parsed = this->parseColor(entry[1], config.lightblue, lineOffset); - } else if ("COLOR_green" == entry[0]) { - parsed = this->parseColor(entry[1], config.green, lineOffset); - } else if ("COLOR_blue" == entry[0]) { - parsed = this->parseColor(entry[1], config.blue, lineOffset); - } else if ("COLOR_lightyellow" == entry[0]) { - parsed = this->parseColor(entry[1], config.lightyellow, lineOffset); - } else if ("COLOR_yellow" == entry[0]) { - parsed = this->parseColor(entry[1], config.yellow, lineOffset); - } else if ("COLOR_orange" == entry[0]) { - parsed = this->parseColor(entry[1], config.orange, lineOffset); - } else if ("COLOR_red" == entry[0]) { - parsed = this->parseColor(entry[1], config.red, lineOffset); - } else if ("COLOR_grey" == entry[0]) { - parsed = this->parseColor(entry[1], config.grey, lineOffset); - } else if ("COLOR_white" == entry[0]) { - parsed = this->parseColor(entry[1], config.white, lineOffset); - } else if ("COLOR_debug" == entry[0]) { - parsed = this->parseColor(entry[1], config.debug, lineOffset); - } else { - this->m_errorLine = lineOffset; - this->m_errorMessage = "Unknown file entry: " + entry[0]; - return false; - } - - if (false == parsed) { - return false; - } - } - - config.valid = true; - return true; -} diff --git a/config/FileFormat.h b/config/FileFormat.h deleted file mode 100644 index ce6f03d..0000000 --- a/config/FileFormat.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include -#include - -#include - -namespace vacdm { - /** - * @brief Defines the base class for all file formats - * @ingroup format - * - * The base class provides error information, etc. - */ - class FileFormat { - protected: - std::uint32_t m_errorLine; /**< Defines the line number if a parser detected an error */ - std::string m_errorMessage; /**< Defines the descriptive error message */ - - /** - * @brief Resets the internal structures - */ - void reset() noexcept; - - private: - bool parseColor(const std::string& block, COLORREF& color, std::uint32_t line); - - public: - /** - * @brief Creates the default file format - */ - FileFormat() noexcept; - - /** - * @brief Checks if an error was found - * @return True if an error was found, else false - */ - bool errorFound() const noexcept; - /** - * @brief Returns the error line - * @return The error line - */ - std::uint32_t errorLine() const noexcept; - /** - * @brief Returns the error message - * @return The error message - */ - const std::string& errorMessage() const noexcept; - - bool parse(const std::string& filename, SystemConfig& config); - }; -} diff --git a/helper/String.h b/helper/String.h deleted file mode 100644 index feb5280..0000000 --- a/helper/String.h +++ /dev/null @@ -1,120 +0,0 @@ -/* - * @brief Defines and implements functions to handle strings - * @file helper/String.h - * @author Sven Czarnian - * @copyright Copyright 2020-2021 Sven Czarnian - * @license This project is published under the GNU General Public License v3 (GPLv3) - */ - -#pragma once - -#include -#include -#include - -namespace vacdm { - namespace helper { - /** - * @brief Implements and defines convinience functions for the string handling - * @ingroup helper - */ - class String { - private: - template - static auto splitAux(const std::string& value, Separator&& separator) -> std::vector { - std::vector result; - std::string::size_type p = 0; - std::string::size_type q; - while ((q = separator(value, p)) != std::string::npos) { - result.emplace_back(value, p, q - p); - p = q + 1; - } - result.emplace_back(value, p); - return result; - } - - public: - String() = delete; - String(const String&) = delete; - String(String&&) = delete; - String& operator=(const String&) = delete; - String& operator=(String&&) = delete; - - /** - * @brief Replaces all markers by replace in message - * @param[in,out] message The message which needs to be modified - * @param[in] marker The wildcard which needs to be found in message and which needs to be replaced - * @param[in] replace The replacement of marker in message - * @return - */ - static __inline void stringReplace(std::string& message, const std::string& marker, const std::string& replace) { - std::size_t pos = message.find(marker, 0); - while (std::string::npos != pos) { - auto it = message.cbegin() + pos; - message.replace(it, it + marker.length(), replace); - pos = message.find(marker, pos + marker.length()); - } - } - - /** - * @brief Splits value into chunks and the separator is defined in separators - * @param[in] value The string which needs to be splitted up - * @param[in] separators The separators which split up the value - * @return The list of splitted chunks - */ - static auto splitString(const std::string& value, const std::string& separators) -> std::vector { - return String::splitAux(value, [&](const std::string& v, std::string::size_type p) noexcept { - return v.find_first_of(separators, p); - }); - } - - /** - * @brief Removes leading and trailing whitespaces - * @param[in] value The trimable string - * @param[in] spaces The characters that need to be removed - * @return The trimmed version of value - */ - static auto trim(const std::string& value, const std::string& spaces = " \t") -> std::string { - const auto begin = value.find_first_not_of(spaces, 0); - if (std::string::npos == begin) - return ""; - - const auto end = value.find_last_not_of(spaces); - const auto range = end - begin + 1; - - return value.substr(begin, range); - } - - /** - * @brief find the first occurrence of a 4-letter ICAO code - * @param[in] value the string to find the ICAO in - * @return the ICAO or "" if none was found - */ - static auto findIcao(std::string input) -> std::string { -#pragma warning(disable : 4244) - std::transform(input.begin(), input.end(), input.begin(), - ::toupper); -#pragma warning(default : 4244) - - // Find the first occurrence of a 4-letter ICAO code - std::size_t found = - input.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); - - while (found != std::string::npos) { - // Check if the substring starting from the found position is - // 4 characters long - if (input.substr(found, 4).length() == 4) { - return input.substr(found, 4); - } - - // Continue searching for the next uppercase letter - found = input.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ", - found + 1); - } - - // Return an empty string if no valid ICAO code is found - return ""; - } - }; - } -} diff --git a/logging/Logger.cpp b/logging/Logger.cpp deleted file mode 100644 index d177cae..0000000 --- a/logging/Logger.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include -#include - -#include "Logger.h" - -using namespace vacdm::logging; - -static const char __loggingTable[] = "CREATE TABLE messages( \ - timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, \ - component TEXT, \ - level INT, \ - message TEXT \ -);"; -static const std::string __insertMessage = "INSERT INTO messages VALUES (CURRENT_TIMESTAMP, @1, @2, @3)"; - -Logger::Logger() : - m_database(), - m_minimumLevel(Logger::Level::Disabled) { - stream << std::format("{0:%Y%m%d%H%M%S}", std::chrono::utc_clock::now()) << ".vacdm"; -} - -Logger::~Logger() { - if (nullptr != this->m_database) - sqlite3_close_v2(this->m_database); -} - -void Logger::setMinimumLevel(Logger::Level level) { - this->m_minimumLevel = level; -} - -void Logger::log(const std::string& component, Logger::Level level, const std::string& message) { - if (level < this->m_minimumLevel || component.length() == 0 || message.length() == 0) { - return; - } - - if (logFileCreated == false) { - createLogFile(); - } - - sqlite3_stmt* stmt; - - sqlite3_prepare_v2(this->m_database, __insertMessage.c_str(), __insertMessage.length(), &stmt, nullptr); - sqlite3_bind_text(stmt, 1, component.c_str(), -1, SQLITE_TRANSIENT); - sqlite3_bind_int(stmt, 2, static_cast(level)); - sqlite3_bind_text(stmt, 3, message.c_str(), -1, SQLITE_TRANSIENT); - - sqlite3_step(stmt); - sqlite3_clear_bindings(stmt); - sqlite3_reset(stmt); -} - -void Logger::createLogFile() { - sqlite3_open(stream.str().c_str(), &this->m_database); - sqlite3_exec(this->m_database, __loggingTable, nullptr, nullptr, nullptr); - sqlite3_exec(this->m_database, "PRAGMA journal_mode = MEMORY", nullptr, nullptr, nullptr); - logFileCreated = true; -} - -Logger& Logger::instance() { - static Logger __instance; - return __instance; -} diff --git a/logging/Logger.h b/logging/Logger.h deleted file mode 100644 index dc06bac..0000000 --- a/logging/Logger.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "sqlite3.h" - -namespace vacdm { -namespace logging { - class Logger { - public: - enum class Level { - Debug, - Info, - Warning, - Error, - Critical, - System, - Disabled - }; - - private: - sqlite3* m_database; - Level m_minimumLevel; - std::stringstream stream; - bool logFileCreated = false; - void createLogFile(); - Logger(); - public: - ~Logger(); - void setMinimumLevel(Level level); - void log(const std::string& component, Level level, const std::string& message); - static Logger& instance(); - }; -} -} diff --git a/logging/Performance.h b/logging/Performance.h deleted file mode 100644 index 39b6745..0000000 --- a/logging/Performance.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "Logger.h" - -#define PERFORMANCE_MEASUREMENT 1 - -namespace vacdm { -namespace logging { - class Performance { - private: - std::string m_name; - std::uint64_t m_average; - std::int64_t m_minimum; - std::int64_t m_maximum; - std::uint32_t m_cycleCount; - std::chrono::high_resolution_clock::time_point m_startTime; - std::chrono::high_resolution_clock::time_point m_lastLogStamp; - - public: - Performance(const std::string& name) : - m_name(name), - m_average(0), - m_minimum(std::numeric_limits::max()), - m_maximum(std::numeric_limits::min()), - m_cycleCount(0), - m_startTime(), - m_lastLogStamp(std::chrono::high_resolution_clock::now()) {} - - void start() { -#if PERFORMANCE_MEASUREMENT - this->m_startTime = std::chrono::high_resolution_clock::now(); -#endif - } - - void stop() { -#if PERFORMANCE_MEASUREMENT - const auto now = std::chrono::high_resolution_clock::now(); - const auto delta = std::chrono::duration_cast(now - this->m_startTime).count(); - - this->m_minimum = std::min(this->m_minimum, delta); - this->m_maximum = std::max(this->m_maximum, delta); - if (this->m_cycleCount == 0) - this->m_average = delta; - else - this->m_average = static_cast((this->m_average + delta) * 0.5); - - if (std::chrono::duration_cast(now - this->m_lastLogStamp).count() >= 1) { - std::stringstream stream; - stream << "Performance statistics:" << std::endl; - stream << " Average: " << std::to_string(this->m_average) << " us" << std::endl; - stream << " Minimum: " << std::to_string(this->m_minimum) << " us" << std::endl; - stream << " Maximum: " << std::to_string(this->m_maximum) << " us" << std::endl; - - Logger::instance().log("Performance-" + this->m_name, Logger::Level::Info, stream.str()); - - this->m_lastLogStamp = now; - } -#endif - } - }; -} -} diff --git a/Version.h.in b/src/Version.h.in similarity index 66% rename from Version.h.in rename to src/Version.h.in index 1797e1d..275ba60 100644 --- a/Version.h.in +++ b/src/Version.h.in @@ -1,12 +1,17 @@ -#pragma once - -namespace { -const char *PLUGIN_NAME{ "vACDM" }; -const char *PLUGIN_VERSION{ "@CMAKE_PROJECT_VERSION@" }; -const char *PLUGIN_AUTHOR{ "-" }; -const char *PLUGIN_LICENSE{ "GPLv3" }; - -static constexpr std::uint8_t PLUGIN_VERSION_MAJOR = @CMAKE_PROJECT_VERSION_MAJOR@; -static constexpr std::uint8_t PLUGIN_VERSION_MINOR = @CMAKE_PROJECT_VERSION_MINOR@; -static constexpr std::uint8_t PLUGIN_VERSION_PATCH = @CMAKE_PROJECT_VERSION_PATCH@; +#pragma once +// clang-format off +// prevents C2018 during compilation +namespace { +const char *PLUGIN_NAME{ "vACDM" }; +#if DEV_BUILD +const char *PLUGIN_VERSION{ "@CMAKE_PROJECT_VERSION@-DEV.@DEV_RELEASE_NUMBER@"}; +#else +const char *PLUGIN_VERSION{ "@CMAKE_PROJECT_VERSION@" }; +#endif +const char *PLUGIN_AUTHOR{ "vACDM Team" }; +const char *PLUGIN_LICENSE{ "GPLv3" }; + +static constexpr std::uint8_t PLUGIN_VERSION_MAJOR = @CMAKE_PROJECT_VERSION_MAJOR@; +static constexpr std::uint8_t PLUGIN_VERSION_MINOR = @CMAKE_PROJECT_VERSION_MINOR@; +static constexpr std::uint8_t PLUGIN_VERSION_PATCH = @CMAKE_PROJECT_VERSION_PATCH@; } \ No newline at end of file diff --git a/src/config/ConfigParser.cpp b/src/config/ConfigParser.cpp new file mode 100644 index 0000000..61de171 --- /dev/null +++ b/src/config/ConfigParser.cpp @@ -0,0 +1,136 @@ +#include "ConfigParser.h" + +#include +#include +#include +#include + +#include "core/DataManager.h" +#include "utils/String.h" + +using namespace vacdm; + +ConfigParser::ConfigParser() : m_errorLine(std::numeric_limits::max()), m_errorMessage() {} + +bool ConfigParser::errorFound() const { return std::numeric_limits::max() != this->m_errorLine; } + +std::uint32_t ConfigParser::errorLine() const { return this->m_errorLine; } + +const std::string &ConfigParser::errorMessage() const { return this->m_errorMessage; } + +bool ConfigParser::parseColor(const std::string &block, COLORREF &color, std::uint32_t line) { + std::vector colorValues = utils::String::splitString(block, ","); + + if (3 != colorValues.size()) { + this->m_errorLine = line; + this->m_errorMessage = "Invalid color config"; + return false; + } + + std::array colors; + for (std::size_t i = 0; i < 3; ++i) colors[i] = static_cast(std::atoi(colorValues[i].c_str())); + + color = RGB(colors[0], colors[1], colors[2]); + + return true; +} + +bool ConfigParser::parse(const std::string &filename, PluginConfig &config) { + config.valid = true; + + std::ifstream stream(filename); + if (stream.is_open() == false) { + this->m_errorMessage = "Unable to open the configuration file"; + this->m_errorLine = 0; + return false; + } + + std::string line; + std::uint32_t lineOffset = 0; + while (std::getline(stream, line)) { + std::string value; + + lineOffset += 1; + + /* skip empty lines */ + if (line.length() == 0) continue; + + /* trim the line and skip comments*/ + std::string trimmed = utils::String::trim(line); + if (trimmed.find_first_of('#', 0) == 0) continue; + + // get values of line + std::vector values = utils::String::splitString(trimmed, "="); + if (values.size() != 2) { + this->m_errorLine = lineOffset; + this->m_errorMessage = "Invalid configuration entry"; + return false; + } else { + value = values[1]; + } + + /* end on invalid lines */ + if (0 == value.length()) { + this->m_errorLine = lineOffset; + this->m_errorMessage = "Invalid entry"; + return false; + } + + bool parsed = false; + if ("SERVER_url" == values[0]) { + config.serverUrl = values[1]; + parsed = true; + } else if ("UPDATE_RATE_SECONDS" == values[0]) { + try { + const int updateCycleSeconds = std::stoi(values[1]); + if (updateCycleSeconds < core::minUpdateCycleSeconds || + updateCycleSeconds > core::maxUpdateCycleSeconds) { + this->m_errorLine = lineOffset; + this->m_errorMessage = "Value must be number between " + + std::to_string(core::minUpdateCycleSeconds) + " and " + + std::to_string(core::maxUpdateCycleSeconds); + } else { + config.updateCycleSeconds = updateCycleSeconds; + parsed = true; + } + } catch (const std::exception &e) { + this->m_errorMessage = e.what(); + this->m_errorLine = lineOffset; + } + + } else if ("COLOR_lightgreen" == values[0]) { + parsed = this->parseColor(values[1], config.lightgreen, lineOffset); + } else if ("COLOR_lightblue" == values[0]) { + parsed = this->parseColor(values[1], config.lightblue, lineOffset); + } else if ("COLOR_green" == values[0]) { + parsed = this->parseColor(values[1], config.green, lineOffset); + } else if ("COLOR_blue" == values[0]) { + parsed = this->parseColor(values[1], config.blue, lineOffset); + } else if ("COLOR_lightyellow" == values[0]) { + parsed = this->parseColor(values[1], config.lightyellow, lineOffset); + } else if ("COLOR_yellow" == values[0]) { + parsed = this->parseColor(values[1], config.yellow, lineOffset); + } else if ("COLOR_orange" == values[0]) { + parsed = this->parseColor(values[1], config.orange, lineOffset); + } else if ("COLOR_red" == values[0]) { + parsed = this->parseColor(values[1], config.red, lineOffset); + } else if ("COLOR_grey" == values[0]) { + parsed = this->parseColor(values[1], config.grey, lineOffset); + } else if ("COLOR_white" == values[0]) { + parsed = this->parseColor(values[1], config.white, lineOffset); + } else if ("COLOR_debug" == values[0]) { + parsed = this->parseColor(values[1], config.debug, lineOffset); + } else { + this->m_errorLine = lineOffset; + this->m_errorMessage = "Unknown file entry: " + value[0]; + return false; + } + + if (false == parsed) { + return false; + } + } + + config.valid = true; + return true; +} \ No newline at end of file diff --git a/src/config/ConfigParser.h b/src/config/ConfigParser.h new file mode 100644 index 0000000..360525d --- /dev/null +++ b/src/config/ConfigParser.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#include "config/PluginConfig.h" + +namespace vacdm { +/// @brief parses the config file .txt to type PluginConfig. Also provides information about errors. +class ConfigParser { + private: + std::uint32_t m_errorLine; /* Defines the line number the error has occurred */ + std::string m_errorMessage; /* The error message to print */ + bool parseColor(const std::string &block, COLORREF &color, std::uint32_t line); + + public: + ConfigParser(); + + bool errorFound() const; + std::uint32_t errorLine() const; + const std::string &errorMessage() const; + + bool parse(const std::string &filename, PluginConfig &config); +}; +} // namespace vacdm \ No newline at end of file diff --git a/src/config/PluginConfig.h b/src/config/PluginConfig.h new file mode 100644 index 0000000..6e4ce53 --- /dev/null +++ b/src/config/PluginConfig.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#pragma warning(push, 0) +#include "EuroScopePlugIn.h" +#pragma warning(pop) + +namespace vacdm { +struct PluginConfig { + bool valid = true; + std::string serverUrl = "https://app.vacdm.net"; + int updateCycleSeconds = 5; + COLORREF lightgreen = RGB(127, 252, 73); + COLORREF lightblue = RGB(53, 218, 235); + COLORREF green = RGB(0, 181, 27); + COLORREF blue = RGB(0, 0, 255); + COLORREF lightyellow = RGB(255, 255, 191); + COLORREF yellow = RGB(255, 255, 0); + COLORREF orange = RGB(255, 153, 0); + COLORREF red = RGB(255, 0, 0); + COLORREF grey = RGB(153, 153, 153); + COLORREF white = RGB(255, 255, 255); + COLORREF debug = RGB(255, 0, 255); +}; +} // namespace vacdm \ No newline at end of file diff --git a/src/config/vacdm.txt b/src/config/vacdm.txt new file mode 100644 index 0000000..e3d4fde --- /dev/null +++ b/src/config/vacdm.txt @@ -0,0 +1,13 @@ +SERVER_url=https://app.vacdm.net +UPDATE_RATE_SECONDS=5 +COLOR_lightgreen=127,252,73 +COLOR_lightblue=53,218,235 +COLOR_green=0,181,27 +COLOR_blue=0,0,255 +COLOR_lightyellow=255,255,191 +COLOR_yellow=255,255,0 +COLOR_orange=255,153,0 +COLOR_red=255,0,0 +COLOR_grey=153,153,153 +COLOR_white=255,255,255 +COLOR_debug=255,0,255 \ No newline at end of file diff --git a/src/core/DataManager.cpp b/src/core/DataManager.cpp new file mode 100644 index 0000000..674e7b3 --- /dev/null +++ b/src/core/DataManager.cpp @@ -0,0 +1,528 @@ +#include "DataManager.h" + +#include "core/Server.h" +#include "log/Logger.h" +#include "utils/Date.h" + +using namespace vacdm::com; +using namespace vacdm::core; +using namespace vacdm::logging; +using namespace std::chrono_literals; + +static constexpr std::size_t ConsolidatedData = 0; +static constexpr std::size_t EuroscopeData = 1; +static constexpr std::size_t ServerData = 2; + +DataManager::DataManager() : m_pause(false), m_stop(false) { this->m_worker = std::thread(&DataManager::run, this); } + +DataManager::~DataManager() { + this->m_stop = true; + this->m_worker.join(); +} + +DataManager& DataManager::instance() { + static DataManager __instance; + return __instance; +} + +bool DataManager::checkPilotExists(const std::string& callsign) { + if (true == this->m_pause) return false; + + std::lock_guard guard(this->m_pilotLock); + return this->m_pilots.cend() != this->m_pilots.find(callsign); +} + +const types::Pilot DataManager::getPilot(const std::string& callsign) { + std::lock_guard guard(this->m_pilotLock); + return this->m_pilots.find(callsign)->second[ConsolidatedData]; +} + +void DataManager::pause() { this->m_pause = true; } + +void DataManager::resume() { this->m_pause = false; } + +std::string DataManager::setUpdateCycleSeconds(const int newUpdateCycleSeconds) { + if (newUpdateCycleSeconds < minUpdateCycleSeconds || newUpdateCycleSeconds > maxUpdateCycleSeconds) + return "Could not set update rate"; + + this->updateCycleSeconds = newUpdateCycleSeconds; + + return "vACDM updating every " + + (newUpdateCycleSeconds == 1 ? "second" : std::to_string(newUpdateCycleSeconds) + " seconds"); +} + +void DataManager::run() { + std::size_t counter = 1; + while (true) { + std::this_thread::sleep_for(1s); + if (true == this->m_stop) return; + if (true == this->m_pause) continue; + + // run every updateCycleSeconds seconds + if (counter++ % updateCycleSeconds != 0) continue; + + // obtain a copy of the pilot data, work with the copy to minimize lock time + this->m_pilotLock.lock(); + auto pilots = this->m_pilots; + this->m_pilotLock.unlock(); + + this->processAsynchronousMessages(pilots); + + this->processEuroScopeUpdates(pilots); + + this->consolidateWithBackend(pilots); + + if (true == Server::instance().getMaster()) { + std::list> transmissionBuffer; + for (auto& pilot : pilots) { + Json::Value message; + const auto sendType = DataManager::deltaEuroscopeToBackend(pilot.second, message); + if (MessageType::None != sendType) + transmissionBuffer.push_back({pilot.second[ConsolidatedData], sendType, message}); + } + + for (const auto& transmission : std::as_const(transmissionBuffer)) { + if (std::get<1>(transmission) == MessageType::Post) + com::Server::instance().postPilot(std::get<0>(transmission)); + else if (std::get<1>(transmission) == MessageType::Patch) + com::Server::instance().sendPatchMessage("/api/v1/pilots/" + std::get<0>(transmission).callsign, + std::get<2>(transmission)); + } + } + + // replace the pilot data with the updated copy + this->m_pilotLock.lock(); + this->m_pilots = pilots; + this->m_pilotLock.unlock(); + } +} + +void DataManager::processAsynchronousMessages(std::map>& pilots) { + this->m_asyncMessagesLock.lock(); + auto messages = this->m_asynchronousMessages; + this->m_asynchronousMessages.clear(); + this->m_asyncMessagesLock.unlock(); + + for (auto& message : messages) { + // find pilot in list + for (auto& [callsign, data] : pilots) { + if (callsign == message.callsign) { + std::string messageType; + + switch (message.type) { + case MessageType::UpdateEXOT: + Server::instance().updateExot(message.callsign, message.value); + messageType = "EXOT"; + break; + case MessageType::UpdateTOBT: + Server::instance().updateTobt(data[ConsolidatedData], message.value, false); + messageType = "TOBT"; + break; + case MessageType::UpdateTOBTConfirmed: + Server::instance().updateTobt(data[ConsolidatedData], message.value, true); + messageType = "TOBT Confirmed Status"; + break; + case MessageType::UpdateASAT: + Server::instance().updateAsat(message.callsign, message.value); + messageType = "ASAT"; + break; + case MessageType::UpdateASRT: + Server::instance().updateAsrt(message.callsign, message.value); + messageType = "ASRT"; + break; + case MessageType::UpdateAOBT: + Server::instance().updateAobt(message.callsign, message.value); + messageType = "AOBT"; + break; + case MessageType::UpdateAORT: + Server::instance().updateAort(message.callsign, message.value); + messageType = "AORT"; + break; + case MessageType::ResetTOBT: + Server::instance().resetTobt(message.callsign, types::defaultTime, + data[ConsolidatedData].tobt_state); + messageType = "TOBT reset"; + break; + case MessageType::ResetASAT: + Server::instance().updateAsat(message.callsign, message.value); + messageType = "ASAT reset"; + break; + case MessageType::ResetASRT: + Server::instance().updateAsrt(message.callsign, message.value); + messageType = "ASRT reset"; + break; + case MessageType::ResetTOBTConfirmed: + Server::instance().resetTobt(message.callsign, data[ConsolidatedData].tobt, "GUESS"); + messageType = "TOBT confirmed reset"; + break; + case MessageType::ResetAORT: + Server::instance().updateAort(message.callsign, message.value); + messageType = "AORT reset"; + break; + case MessageType::ResetAOBT: + Server::instance().updateAobt(message.callsign, message.value); + messageType = "AOBT reset"; + break; + case MessageType::ResetPilot: + Server::instance().deletePilot(message.callsign); + messageType = "Pilot reset"; + break; + + default: + break; + } + + Logger::instance().log(Logger::LogSender::DataManager, + "Sending " + messageType + " update: " + callsign + " - " + + utils::Date::timestampToIsoString(message.value), + Logger::LogLevel::Info); + + break; + } + } + } +} + +void DataManager::handleTagFunction(MessageType type, const std::string callsign, + const std::chrono::utc_clock::time_point value) { + // do not handle the tag function if the aircraft does not exist or the client is not master + if (false == this->checkPilotExists(callsign) || false == Server::instance().getMaster()) return; + + // queue the update message which will be sent to the backend + { + std::lock_guard guard(this->m_asyncMessagesLock); + this->m_asynchronousMessages.push_back({type, callsign, value}); + } + + // set the data locally, gives feedback to user that the action was handled, might get overwritten again in the + // update cycle if the backend does not accept the message + std::lock_guard guard(this->m_pilotLock); + auto it = this->m_pilots.find(callsign); + auto& pilot = it->second[ConsolidatedData]; + + pilot.lastUpdate = std::chrono::utc_clock::now(); + + switch (type) { + case MessageType::UpdateEXOT: + pilot.exot = value; + pilot.tsat = types::defaultTime; + pilot.ttot = types::defaultTime; + pilot.asat = types::defaultTime; + pilot.aobt = types::defaultTime; + pilot.atot = types::defaultTime; + break; + case MessageType::UpdateTOBT: { + bool resetTsat = value >= pilot.tsat; + + pilot.tobt = value; + if (true == resetTsat) pilot.tsat = types::defaultTime; + pilot.ttot = types::defaultTime; + pilot.exot = types::defaultTime; + pilot.asat = types::defaultTime; + pilot.aobt = types::defaultTime; + pilot.atot = types::defaultTime; + + break; + } + case MessageType::UpdateTOBTConfirmed: { + bool resetTsat = value == types::defaultTime || value >= pilot.tsat; + + pilot.tobt = value; + if (true == resetTsat) pilot.tsat = types::defaultTime; + pilot.ttot = types::defaultTime; + pilot.exot = types::defaultTime; + pilot.asat = types::defaultTime; + pilot.aobt = types::defaultTime; + pilot.atot = types::defaultTime; + + break; + } + case MessageType::UpdateASAT: + pilot.asat = value; + break; + case MessageType::UpdateASRT: + pilot.asrt = value; + break; + case MessageType::UpdateAOBT: + pilot.aobt = value; + break; + case MessageType::UpdateAORT: + pilot.aort = value; + break; + case MessageType::ResetTOBT: + pilot.tobt = types::defaultTime; + pilot.tsat = types::defaultTime; + pilot.ttot = types::defaultTime; + pilot.exot = types::defaultTime; + pilot.asat = types::defaultTime; + pilot.asrt = types::defaultTime; + pilot.aobt = types::defaultTime; + pilot.aort = types::defaultTime; + pilot.atot = types::defaultTime; + break; + case MessageType::ResetASAT: + pilot.asat = types::defaultTime; + break; + case MessageType::ResetASRT: + pilot.asrt = types::defaultTime; + break; + case MessageType::ResetTOBTConfirmed: + pilot.tobt_state = "GUESS"; + break; + case MessageType::ResetAORT: + pilot.aort = types::defaultTime; + break; + case MessageType::ResetAOBT: + pilot.aobt = types::defaultTime; + break; + case MessageType::ResetPilot: + this->m_pilots.erase(it); + break; + default: + break; + } +} + +DataManager::MessageType DataManager::deltaEuroscopeToBackend(const std::array& data, + Json::Value& message) { + message.clear(); + + if (data[ServerData].callsign == "" && data[EuroscopeData].callsign != "") { + return DataManager::MessageType::Post; + } else { + message["callsign"] = data[EuroscopeData].callsign; + + int deltaCount = 0; + + if (data[EuroscopeData].inactive != data[ServerData].inactive) { + message["inactive"] = data[EuroscopeData].inactive; + deltaCount += 1; + } + + auto lastDelta = deltaCount; + message["position"] = Json::Value(); + if (data[EuroscopeData].latitude != data[ServerData].latitude) { + message["position"]["lat"] = data[EuroscopeData].latitude; + deltaCount += 1; + } + if (data[EuroscopeData].longitude != data[ServerData].longitude) { + message["position"]["lon"] = data[EuroscopeData].longitude; + deltaCount += 1; + } + if (deltaCount == lastDelta) message.removeMember("position"); + + // patch flightplan data + lastDelta = deltaCount; + message["flightplan"] = Json::Value(); + if (data[EuroscopeData].origin != data[ServerData].origin) { + deltaCount += 1; + message["flightplan"]["departure"] = data[EuroscopeData].origin; + } + if (data[EuroscopeData].destination != data[ServerData].destination) { + deltaCount += 1; + message["flightplan"]["arrival"] = data[EuroscopeData].destination; + } + if (deltaCount == lastDelta) message.removeMember("flightplan"); + + // patch clearance data + lastDelta = deltaCount; + message["clearance"] = Json::Value(); + if (data[EuroscopeData].runway != data[ServerData].runway) { + deltaCount += 1; + message["clearance"]["dep_rwy"] = data[EuroscopeData].runway; + } + if (data[EuroscopeData].sid != data[ServerData].sid) { + deltaCount += 1; + message["clearance"]["sid"] = data[EuroscopeData].sid; + } + if (deltaCount == lastDelta) message.removeMember("clearance"); + + return deltaCount != 0 ? DataManager::MessageType::Patch : DataManager::MessageType::None; + } +} + +void DataManager::setActiveAirports(const std::list activeAirports) { + std::lock_guard guard(this->m_airportLock); + this->m_activeAirports = activeAirports; +} + +void DataManager::queueFlightplanUpdate(EuroScopePlugIn::CFlightPlan flightplan) { + if (false == flightplan.IsValid()) return; + std::lock_guard guard(this->m_euroscopeUpdatesLock); + this->m_euroscopeFlightplanUpdates.push_back({std::chrono::utc_clock::now(), flightplan}); +} + +void DataManager::consolidateWithBackend(std::map>& pilots) { + // retrieving backend data + auto backendPilots = Server::instance().getPilots(this->m_activeAirports); + + for (auto pilot = pilots.begin(); pilots.end() != pilot;) { + // update backend data & consolidate + bool removeFlight = pilot->second[ServerData].inactive == true; + for (auto updateIt = backendPilots.begin(); updateIt != backendPilots.end(); ++updateIt) { + if (updateIt->callsign == pilot->second[EuroscopeData].callsign) { + Logger::instance().log( + Logger::LogSender::DataManager, + "Updating " + pilot->second[EuroscopeData].callsign + " with" + updateIt->callsign, + Logger::LogLevel::Info); + pilot->second[ServerData] = *updateIt; + DataManager::consolidateData(pilot->second); + removeFlight = false; + updateIt = backendPilots.erase(updateIt); + break; + } + } + + // remove pilot if he has been flagged as inactive from the backend + if (true == removeFlight) { + pilot = pilots.erase(pilot); + } else { + ++pilot; + } + } +} + +void DataManager::consolidateData(std::array& pilot) { + if (pilot[EuroscopeData].callsign == pilot[ServerData].callsign) { + // backend data + pilot[ConsolidatedData].inactive = pilot[ServerData].inactive; + pilot[ConsolidatedData].lastUpdate = pilot[ServerData].lastUpdate; + + pilot[ConsolidatedData].eobt = pilot[ServerData].eobt; + pilot[ConsolidatedData].tobt = pilot[ServerData].tobt; + pilot[ConsolidatedData].tobt_state = pilot[ServerData].tobt_state; + pilot[ConsolidatedData].ctot = pilot[ServerData].ctot; + pilot[ConsolidatedData].ttot = pilot[ServerData].ttot; + pilot[ConsolidatedData].tsat = pilot[ServerData].tsat; + pilot[ConsolidatedData].exot = pilot[ServerData].exot; + pilot[ConsolidatedData].asat = pilot[ServerData].asat; + pilot[ConsolidatedData].aobt = pilot[ServerData].aobt; + pilot[ConsolidatedData].atot = pilot[ServerData].atot; + pilot[ConsolidatedData].asrt = pilot[ServerData].asrt; + pilot[ConsolidatedData].aort = pilot[ServerData].aort; + + pilot[ConsolidatedData].measures = pilot[ServerData].measures; + pilot[ConsolidatedData].hasBooking = pilot[ServerData].hasBooking; + pilot[ConsolidatedData].taxizoneIsTaxiout = pilot[ServerData].taxizoneIsTaxiout; + + // EuroScope data + pilot[ConsolidatedData].latitude = pilot[EuroscopeData].latitude; + pilot[ConsolidatedData].longitude = pilot[EuroscopeData].longitude; + + pilot[ConsolidatedData].origin = pilot[EuroscopeData].origin; + pilot[ConsolidatedData].destination = pilot[EuroscopeData].destination; + pilot[ConsolidatedData].runway = pilot[EuroscopeData].runway; + pilot[ConsolidatedData].sid = pilot[EuroscopeData].sid; + + logging::Logger::instance().log(Logger::LogSender::DataManager, "Consolidated " + pilot[ServerData].callsign, + logging::Logger::LogLevel::Info); + } else { + logging::Logger::instance().log(Logger::LogSender::DataManager, + "Callsign mismatch during consolidation: " + pilot[EuroscopeData].callsign + + ", " + pilot[ServerData].callsign, + logging::Logger::LogLevel::Critical); + } +} + +void DataManager::processEuroScopeUpdates(std::map>& pilots) { + // obtain a copy of the flightplan updates, clear the update list, consolidate flightplan updates + this->m_euroscopeUpdatesLock.lock(); + auto flightplanUpdates = this->m_euroscopeFlightplanUpdates; + this->m_euroscopeFlightplanUpdates.clear(); + this->m_euroscopeUpdatesLock.unlock(); + + this->consolidateFlightplanUpdates(flightplanUpdates); + + for (auto& update : flightplanUpdates) { + bool found = false; + + auto pilot = DataManager::CFlightPlanToPilot(update.data); + + // find pilot in list + for (auto& pair : pilots) { + if (pilot.callsign == pair.first) { + Logger::instance().log(Logger::LogSender::DataManager, "Updated data of " + pilot.callsign, + Logger::LogLevel::Info); + + pair.second[EuroscopeData] = pilot; + found = true; + break; + } + } + + if (false == found) { + Logger::instance().log(Logger::LogSender::DataManager, "Added " + pilot.callsign, Logger::LogLevel::Info); + pilots.insert({pilot.callsign, {pilot, pilot, types::Pilot()}}); + } + } +} + +void DataManager::consolidateFlightplanUpdates(std::list& inputList) { + std::list resultList; + + for (const auto& currentUpdate : inputList) { + auto& flightplan = currentUpdate.data; + // only handle updates for active airports + { + std::lock_guard guard(this->m_airportLock); + bool flightDepartsFromActiveAirport = + std::find(m_activeAirports.begin(), m_activeAirports.end(), + std::string(flightplan.GetFlightPlanData().GetOrigin())) != m_activeAirports.end(); + if (false == flightDepartsFromActiveAirport) continue; + } + + // Check if the flight plan already exists in the result list + auto it = std::find_if(resultList.begin(), resultList.end(), + [¤tUpdate](const EuroscopeFlightplanUpdate& existingUpdate) { + return existingUpdate.data.GetCallsign() == currentUpdate.data.GetCallsign(); + }); + + if (it != resultList.end()) { + // Flight plan with the same callsign exists + // Check if the timeIssued is newer + if (currentUpdate.timeIssued > it->timeIssued) { + // Update with the newer data + *it = currentUpdate; + Logger::instance().log(Logger::LogSender::DataManager, + "Updated: " + std::string(currentUpdate.data.GetCallsign()), + Logger::LogLevel::Info); + } else { + // Existing data is already newer, no update needed + Logger::instance().log(Logger::LogSender::DataManager, + "Skipped old update for: " + std::string(currentUpdate.data.GetCallsign()), + Logger::LogLevel::Info); + } + } else { + // Flight plan with the callsign doesn't exist, add it to the result list + resultList.push_back(currentUpdate); + Logger::instance().log(Logger::LogSender::DataManager, + "Update added: " + std::string(currentUpdate.data.GetCallsign()), + Logger::LogLevel::Info); + } + } + + inputList = resultList; +} + +types::Pilot DataManager::CFlightPlanToPilot(const EuroScopePlugIn::CFlightPlan flightplan) { + types::Pilot pilot; + + pilot.callsign = flightplan.GetCallsign(); + pilot.lastUpdate = std::chrono::utc_clock::now(); + + // position data + pilot.latitude = flightplan.GetFPTrackPosition().GetPosition().m_Latitude; + pilot.longitude = flightplan.GetFPTrackPosition().GetPosition().m_Longitude; + + // flightplan & clearance data + pilot.origin = flightplan.GetFlightPlanData().GetOrigin(); + pilot.destination = flightplan.GetFlightPlanData().GetDestination(); + pilot.runway = flightplan.GetFlightPlanData().GetDepartureRwy(); + pilot.sid = flightplan.GetFlightPlanData().GetSidName(); + + // acdm data + pilot.eobt = utils::Date::convertEuroscopeDepartureTime(flightplan); + pilot.tobt = pilot.eobt; + + return pilot; +} \ No newline at end of file diff --git a/src/core/DataManager.h b/src/core/DataManager.h new file mode 100644 index 0000000..25c042d --- /dev/null +++ b/src/core/DataManager.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#include +#include + +#pragma warning(push, 0) +#include "EuroScopePlugIn.h" +#pragma warning(pop) + +#include + +#include "types/Pilot.h" + +using namespace vacdm; + +namespace vacdm::core { + +constexpr int maxUpdateCycleSeconds = 10; +constexpr int minUpdateCycleSeconds = 1; +class DataManager { + private: + DataManager(); + + std::thread m_worker; + bool m_pause; + bool m_stop; + + void run(); + int updateCycleSeconds = 5; + + public: + ~DataManager(); + DataManager(const DataManager &) = delete; + DataManager(DataManager &&) = delete; + + DataManager &operator=(const DataManager &) = delete; + DataManager &operator=(DataManager &&) = delete; + static DataManager &instance(); + + std::string setUpdateCycleSeconds(const int newUpdateCycleSeconds); + + enum class MessageType { + None, + Post, + Patch, + UpdateEXOT, + UpdateTOBT, + UpdateTOBTConfirmed, + UpdateASAT, + UpdateASRT, + UpdateAOBT, + UpdateAORT, + ResetTOBT, + ResetASAT, + ResetASRT, + ResetTOBTConfirmed, + ResetAORT, + ResetAOBT, + ResetPilot + }; + + private: + std::mutex m_pilotLock; + std::map> m_pilots; + std::mutex m_airportLock; + std::list m_activeAirports; + + struct EuroscopeFlightplanUpdate { + std::chrono::utc_clock::time_point timeIssued; + EuroScopePlugIn::CFlightPlan data; + }; + + std::mutex m_euroscopeUpdatesLock; + std::list m_euroscopeFlightplanUpdates; + + /// @brief consolidates all flightplan updates by throwing out old updates and keeping the most current ones + /// @param list of flightplans to consolidate + void consolidateFlightplanUpdates(std::list &list); + /// @brief updates the pilots with the saved EuroScope flightplan updates + /// @param pilots to update + void processEuroScopeUpdates(std::map> &pilots); + /// @brief gathers all information from EuroScope::CFlightPlan and converts it to type Pilot + types::Pilot CFlightPlanToPilot(const EuroScopePlugIn::CFlightPlan flightplan); + /// @brief updates the local data with the data from the backend + /// @param pilots to update + void consolidateWithBackend(std::map> &pilots); + /// @brief consolidates EuroScope and backend data + /// @param pilot + void consolidateData(std::array &pilot); + + MessageType deltaEuroscopeToBackend(const std::array &data, Json::Value &message); + + struct AsynchronousMessage { + const MessageType type; + const std::string callsign; + const std::chrono::utc_clock::time_point value; + }; + + std::mutex m_asyncMessagesLock; + std::list m_asynchronousMessages; + void processAsynchronousMessages(std::map> &pilots); + + public: + void setActiveAirports(const std::list activeAirports); + void queueFlightplanUpdate(EuroScopePlugIn::CFlightPlan flightplan); + void handleTagFunction(MessageType message, const std::string callsign, + const std::chrono::utc_clock::time_point value); + + bool checkPilotExists(const std::string &callsign); + const types::Pilot getPilot(const std::string &callsign); + void pause(); + void resume(); +}; +} // namespace vacdm::core diff --git a/src/core/Server.cpp b/src/core/Server.cpp new file mode 100644 index 0000000..b8ef855 --- /dev/null +++ b/src/core/Server.cpp @@ -0,0 +1,496 @@ +#include "Server.h" + +#include + +#include "Version.h" +#include "log/Logger.h" +#include "utils/Date.h" + +using namespace vacdm; +using namespace vacdm::com; +using namespace vacdm::logging; + +static std::string __receivedDeleteData; +static std::string __receivedGetData; +static std::string __receivedPatchData; +static std::string __receivedPostData; + +static std::size_t receiveCurlDelete(void* ptr, std::size_t size, std::size_t nmemb, void* stream) { + (void)stream; + + std::string serverResult = static_cast(ptr); + __receivedDeleteData += serverResult; + return size * nmemb; +} + +static std::size_t receiveCurlGet(void* ptr, std::size_t size, std::size_t nmemb, void* stream) { + (void)stream; + + std::string serverResult = static_cast(ptr); + __receivedGetData += serverResult; + return size * nmemb; +} + +static std::size_t receiveCurlPatch(void* ptr, std::size_t size, std::size_t nmemb, void* stream) { + (void)stream; + + std::string serverResult = static_cast(ptr); + __receivedPatchData += serverResult; + return size * nmemb; +} + +static std::size_t receiveCurlPost(void* ptr, std::size_t size, std::size_t nmemb, void* stream) { + (void)stream; + + std::string serverResult = static_cast(ptr); + __receivedPostData += serverResult; + return size * nmemb; +} + +Server::Server() + : m_authToken(), + m_getRequest(), + m_postRequest(), + m_patchRequest(), + m_deleteRequest(), + m_apiIsChecked(false), + m_apiIsValid(false), + m_baseUrl("https://app.vacdm.net"), + m_clientIsMaster(false), + m_errorCode() { + /* configure the get request */ + curl_easy_setopt(m_getRequest.socket, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(m_getRequest.socket, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(m_getRequest.socket, CURLOPT_HTTP_VERSION, static_cast(CURL_HTTP_VERSION_1_1)); + curl_easy_setopt(m_getRequest.socket, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(m_getRequest.socket, CURLOPT_WRITEFUNCTION, receiveCurlGet); + curl_easy_setopt(m_getRequest.socket, CURLOPT_TIMEOUT, 2L); + + /* configure the post request */ + curl_easy_setopt(m_postRequest.socket, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(m_postRequest.socket, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(m_postRequest.socket, CURLOPT_HTTP_VERSION, static_cast(CURL_HTTP_VERSION_1_1)); + curl_easy_setopt(m_postRequest.socket, CURLOPT_WRITEFUNCTION, receiveCurlPost); + curl_easy_setopt(m_postRequest.socket, CURLOPT_CUSTOMREQUEST, "POST"); + curl_easy_setopt(m_postRequest.socket, CURLOPT_VERBOSE, 1); + struct curl_slist* headers = nullptr; + headers = curl_slist_append(headers, "Accept: application/json"); + headers = curl_slist_append(headers, ("Authorization: Bearer " + this->m_authToken).c_str()); + headers = curl_slist_append(headers, "Content-Type: application/json"); + curl_easy_setopt(m_postRequest.socket, CURLOPT_HTTPHEADER, headers); + + /* configure the patch request */ + curl_easy_setopt(m_patchRequest.socket, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(m_patchRequest.socket, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(m_patchRequest.socket, CURLOPT_HTTP_VERSION, static_cast(CURL_HTTP_VERSION_1_1)); + curl_easy_setopt(m_patchRequest.socket, CURLOPT_WRITEFUNCTION, receiveCurlPatch); + curl_easy_setopt(m_patchRequest.socket, CURLOPT_CUSTOMREQUEST, "PATCH"); + curl_easy_setopt(m_patchRequest.socket, CURLOPT_VERBOSE, 1); + curl_easy_setopt(m_patchRequest.socket, CURLOPT_HTTPHEADER, headers); + + /* configure the delete request */ + curl_easy_setopt(m_deleteRequest.socket, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(m_deleteRequest.socket, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(m_deleteRequest.socket, CURLOPT_HTTP_VERSION, static_cast(CURL_HTTP_VERSION_1_1)); + curl_easy_setopt(m_deleteRequest.socket, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_easy_setopt(m_deleteRequest.socket, CURLOPT_WRITEFUNCTION, receiveCurlDelete); + curl_easy_setopt(m_deleteRequest.socket, CURLOPT_TIMEOUT, 2L); +} + +Server::~Server() { + if (nullptr != m_getRequest.socket) { + std::lock_guard guard(m_getRequest.lock); + curl_easy_cleanup(m_getRequest.socket); + m_getRequest.socket = nullptr; + } + + if (nullptr != m_postRequest.socket) { + std::lock_guard guard(m_postRequest.lock); + curl_easy_cleanup(m_postRequest.socket); + m_postRequest.socket = nullptr; + } + + if (nullptr != m_patchRequest.socket) { + std::lock_guard guard(m_patchRequest.lock); + curl_easy_cleanup(m_patchRequest.socket); + m_patchRequest.socket = nullptr; + } + + if (nullptr != m_deleteRequest.socket) { + std::lock_guard guard(m_deleteRequest.lock); + curl_easy_cleanup(m_deleteRequest.socket); + m_deleteRequest.socket = nullptr; + } +} + +void Server::changeServerAddress(const std::string& url) { + this->m_baseUrl = url; + this->m_apiIsChecked = false; + this->m_apiIsValid = false; +} + +bool Server::checkWebApi() { + if (this->m_apiIsChecked == true) return this->m_apiIsValid; + + std::lock_guard guard(m_getRequest.lock); + if (m_getRequest.socket == nullptr) { + this->m_apiIsValid = false; + return m_apiIsValid; + } + + __receivedGetData.clear(); + + std::string url = m_baseUrl + "/api/v1/version"; + curl_easy_setopt(m_getRequest.socket, CURLOPT_URL, url.c_str()); + + // send the GET request + CURLcode result = curl_easy_perform(m_getRequest.socket); + if (result != CURLE_OK) { + this->m_apiIsValid = false; + return m_apiIsValid; + } + + Json::CharReaderBuilder builder{}; + auto reader = std::unique_ptr(builder.newCharReader()); + std::string errors; + Json::Value root; + Logger::instance().log(Logger::LogSender::Server, "Received API-version-message: " + __receivedGetData, + Logger::LogLevel::Info); + if (reader->parse(__receivedGetData.c_str(), __receivedGetData.c_str() + __receivedGetData.length(), &root, + &errors)) { + if (PLUGIN_VERSION_MAJOR != root.get("major", Json::Value(-1)).asInt()) { + this->m_errorCode = "Backend-version is incompatible. Please update the plugin."; + this->m_apiIsValid = false; + } else { + this->m_apiIsValid = true; + } + + } else { + this->m_errorCode = "Invalid backend-version response: " + __receivedGetData; + this->m_apiIsValid = false; + } + m_apiIsChecked = true; + return this->m_apiIsValid; +} + +Server::ServerConfiguration Server::getServerConfig() { + if (false == this->m_apiIsChecked || false == this->m_apiIsValid) return Server::ServerConfiguration(); + + std::lock_guard guard(m_getRequest.lock); + if (nullptr != m_getRequest.socket) { + __receivedGetData.clear(); + + std::string url = m_baseUrl + "/api/v1/config"; + curl_easy_setopt(m_getRequest.socket, CURLOPT_URL, url.c_str()); + + /* send the command */ + CURLcode result = curl_easy_perform(m_getRequest.socket); + if (CURLE_OK == result) { + Json::CharReaderBuilder builder{}; + auto reader = std::unique_ptr(builder.newCharReader()); + std::string errors; + Json::Value root; + + Logger::instance().log(Logger::LogSender::Server, "Received configuration: " + __receivedGetData, + Logger::LogLevel::Info); + if (reader->parse(__receivedGetData.c_str(), __receivedGetData.c_str() + __receivedGetData.length(), &root, + &errors)) { + ServerConfiguration_t config; + config.name = root["serverName"].asString(); + config.allowMasterInSweatbox = root["allowSimSession"].asBool(); + config.allowMasterAsObserver = root["allowObsMaster"].asBool(); + return config; + } + } + } + + return ServerConfiguration(); +} + +std::list Server::getPilots(const std::list airports) { + std::lock_guard guard(m_getRequest.lock); + if (nullptr != m_getRequest.socket) { + __receivedGetData.clear(); + + std::string url = m_baseUrl + "/api/v1/pilots"; + if (airports.size() != 0) { + url += "?adep=" + + std::accumulate(std::next(airports.begin()), airports.end(), airports.front(), + [](const std::string& acc, const std::string& str) { return acc + "&adep=" + str; }); + } + Logger::instance().log(Logger::LogSender::Server, url, Logger::LogLevel::Info); + + curl_easy_setopt(m_getRequest.socket, CURLOPT_URL, url.c_str()); + + // send GET request + CURLcode result = curl_easy_perform(m_getRequest.socket); + if (result == CURLE_OK) { + Json::CharReaderBuilder builder{}; + auto reader = std::unique_ptr(builder.newCharReader()); + std::string errors; + Json::Value root; + + // Logger::instance().log(Logger::LogSender::Server, "Received data" + __receivedGetData, + // Logger::LogLevel::Debug); + if (reader->parse(__receivedGetData.c_str(), __receivedGetData.c_str() + __receivedGetData.length(), &root, + &errors) && + root.isArray()) { + std::list pilots; + + for (const auto& pilot : std::as_const(root)) { + pilots.push_back(types::Pilot()); + + pilots.back().callsign = pilot["callsign"].asString(); + pilots.back().lastUpdate = utils::Date::isoStringToTimestamp(pilot["updatedAt"].asString()); + pilots.back().inactive = pilot["inactive"].asBool(); + + // position data + pilots.back().latitude = pilot["position"]["lat"].asDouble(); + pilots.back().longitude = pilot["position"]["lon"].asDouble(); + pilots.back().taxizoneIsTaxiout = pilot["vacdm"]["taxizoneIsTaxiout"].asBool(); + + // flightplan & clearance data + pilots.back().origin = pilot["flightplan"]["departure"].asString(); + pilots.back().destination = pilot["flightplan"]["arrival"].asString(); + pilots.back().runway = pilot["clearance"]["dep_rwy"].asString(); + pilots.back().sid = pilot["clearance"]["sid"].asString(); + + // ACDM procedure data + pilots.back().eobt = utils::Date::isoStringToTimestamp(pilot["vacdm"]["eobt"].asString()); + pilots.back().tobt = utils::Date::isoStringToTimestamp(pilot["vacdm"]["tobt"].asString()); + pilots.back().tobt_state = pilot["vacdm"]["tobt_state"].asString(); + pilots.back().ctot = utils::Date::isoStringToTimestamp(pilot["vacdm"]["ctot"].asString()); + pilots.back().ttot = utils::Date::isoStringToTimestamp(pilot["vacdm"]["ttot"].asString()); + pilots.back().tsat = utils::Date::isoStringToTimestamp(pilot["vacdm"]["tsat"].asString()); + pilots.back().exot = + std::chrono::utc_clock::time_point(std::chrono::minutes(pilot["vacdm"]["exot"].asInt64())); + pilots.back().asat = utils::Date::isoStringToTimestamp(pilot["vacdm"]["asat"].asString()); + pilots.back().aobt = utils::Date::isoStringToTimestamp(pilot["vacdm"]["aobt"].asString()); + pilots.back().atot = utils::Date::isoStringToTimestamp(pilot["vacdm"]["atot"].asString()); + pilots.back().asrt = utils::Date::isoStringToTimestamp(pilot["vacdm"]["asrt"].asString()); + pilots.back().aort = utils::Date::isoStringToTimestamp(pilot["vacdm"]["aort"].asString()); + + // ECFMP measures + Json::Value measuresArray = pilot["measures"]; + std::vector parsedMeasures; + for (const auto& measureObject : std::as_const(measuresArray)) { + vacdm::types::EcfmpMeasure measure; + + measure.ident = measureObject["ident"].asString(); + measure.value = measureObject["value"].asInt(); + + parsedMeasures.push_back(measure); + } + pilots.back().measures = parsedMeasures; + + // event booking data + pilots.back().hasBooking = pilot["hasBooking"].asBool(); + } + Logger::instance().log(Logger::LogSender::Server, "Pilots size: " + std::to_string(pilots.size()), + Logger::LogLevel::Info); + return pilots; + } else { + Logger::instance().log(Logger::LogSender::Server, "Error " + errors, Logger::LogLevel::Info); + } + } + } + + return {}; +} + +void Server::sendPostMessage(const std::string& endpointUrl, const Json::Value& root) { + if (this->m_apiIsChecked == false || this->m_apiIsValid == false || this->m_clientIsMaster == false) return; + + Json::StreamWriterBuilder builder{}; + const auto message = Json::writeString(builder, root); + + Logger::instance().log(Logger::LogSender::Server, + "Posting " + root["callsign"].asString() + " with message: " + message, + Logger::LogLevel::Debug); + + std::lock_guard guard(this->m_postRequest.lock); + if (m_postRequest.socket != nullptr) { + std::string url = m_baseUrl + endpointUrl; + curl_easy_setopt(m_postRequest.socket, CURLOPT_URL, url.c_str()); + curl_easy_setopt(m_postRequest.socket, CURLOPT_POSTFIELDS, message.c_str()); + + curl_easy_perform(m_postRequest.socket); + + Logger::instance().log(Logger::LogSender::Server, + "Posted " + root["callsign"].asString() + " response: " + __receivedPostData, + Logger::LogLevel::Debug); + __receivedPostData.clear(); + } +} + +void Server::sendPatchMessage(const std::string& endpointUrl, const Json::Value& root) { + if (this->m_apiIsChecked == false || this->m_apiIsValid == false || this->m_clientIsMaster == false) return; + + Json::StreamWriterBuilder builder{}; + const auto message = Json::writeString(builder, root); + + Logger::instance().log(Logger::LogSender::Server, + "Patching " + root["callsign"].asString() + " with message: " + message, + Logger::LogLevel::Debug); + + std::lock_guard guard(this->m_patchRequest.lock); + if (m_patchRequest.socket != nullptr) { + std::string url = m_baseUrl + endpointUrl; + curl_easy_setopt(m_patchRequest.socket, CURLOPT_URL, url.c_str()); + curl_easy_setopt(m_patchRequest.socket, CURLOPT_POSTFIELDS, message.c_str()); + + curl_easy_perform(m_patchRequest.socket); + + Logger::instance().log(Logger::LogSender::Server, + "Patched " + root["callsign"].asString() + " response: " + __receivedPatchData, + Logger::LogLevel::Debug); + __receivedPatchData.clear(); + } +} + +void Server::sendDeleteMessage(const std::string& endpointUrl) { + if (this->m_apiIsChecked == false || this->m_apiIsValid == false || this->m_clientIsMaster == false) return; + + Json::StreamWriterBuilder builder{}; + + std::lock_guard guard(this->m_deleteRequest.lock); + if (m_deleteRequest.socket != nullptr) { + std::string url = m_baseUrl + endpointUrl; + + curl_easy_setopt(m_deleteRequest.socket, CURLOPT_URL, url.c_str()); + + curl_easy_perform(m_deleteRequest.socket); + __receivedDeleteData.clear(); + } +} + +void Server::postPilot(types::Pilot pilot) { + Json::Value root; + + root["callsign"] = pilot.callsign; + root["inactive"] = false; + + root["position"] = Json::Value(); + root["position"]["lat"] = pilot.latitude; + root["position"]["lon"] = pilot.longitude; + + root["flightplan"] = Json::Value(); + root["flightplan"]["departure"] = pilot.origin; + root["flightplan"]["arrival"] = pilot.destination; + + root["vacdm"] = Json::Value(); + root["vacdm"]["eobt"] = utils::Date::timestampToIsoString(pilot.eobt); + root["vacdm"]["tobt"] = utils::Date::timestampToIsoString(pilot.tobt); + + root["clearance"] = Json::Value(); + root["clearance"]["dep_rwy"] = pilot.runway; + root["clearance"]["sid"] = pilot.sid; + + this->sendPostMessage("/api/v1/pilots", root); +} + +void Server::updateExot(const std::string& callsign, const std::chrono::utc_clock::time_point& exot) { + Json::Value root; + + root["callsign"] = callsign; + root["vacdm"] = Json::Value(); + root["vacdm"]["exot"] = std::chrono::duration_cast(exot.time_since_epoch()).count(); + root["vacdm"]["tsat"] = utils::Date::timestampToIsoString(types::defaultTime); + root["vacdm"]["ttot"] = utils::Date::timestampToIsoString(types::defaultTime); + root["vacdm"]["asat"] = utils::Date::timestampToIsoString(types::defaultTime); + root["vacdm"]["aobt"] = utils::Date::timestampToIsoString(types::defaultTime); + root["vacdm"]["atot"] = utils::Date::timestampToIsoString(types::defaultTime); + + this->sendPatchMessage("/api/v1/pilots/" + callsign, root); +} + +void Server::updateTobt(const types::Pilot& pilot, const std::chrono::utc_clock::time_point& tobt, bool manualTobt) { + Json::Value root; + + bool resetTsat = (tobt == types::defaultTime && true == manualTobt) || tobt >= pilot.tsat; + root["callsign"] = pilot.callsign; + root["vacdm"] = Json::Value(); + + root["vacdm"] = Json::Value(); + root["vacdm"]["tobt"] = utils::Date::timestampToIsoString(tobt); + if (true == resetTsat) root["vacdm"]["tsat"] = utils::Date::timestampToIsoString(types::defaultTime); + if (false == manualTobt) root["vacdm"]["tobt_state"] = "CONFIRMED"; + + root["vacdm"]["ttot"] = utils::Date::timestampToIsoString(types::defaultTime); + root["vacdm"]["asat"] = utils::Date::timestampToIsoString(types::defaultTime); + root["vacdm"]["aobt"] = utils::Date::timestampToIsoString(types::defaultTime); + root["vacdm"]["atot"] = utils::Date::timestampToIsoString(types::defaultTime); + + this->sendPatchMessage("/api/v1/pilots/" + pilot.callsign, root); +} + +void Server::updateAsat(const std::string& callsign, const std::chrono::utc_clock::time_point& asat) { + Json::Value root; + + root["callsign"] = callsign; + root["vacdm"] = Json::Value(); + root["vacdm"]["asat"] = utils::Date::timestampToIsoString(asat); + + this->sendPatchMessage("/api/v1/pilots/" + callsign, root); +} + +void Server::updateAsrt(const std::string& callsign, const std::chrono::utc_clock::time_point& asrt) { + Json::Value root; + + root["callsign"] = callsign; + root["vacdm"] = Json::Value(); + root["vacdm"]["asrt"] = utils::Date::timestampToIsoString(asrt); + + this->sendPatchMessage("/api/v1/pilots/" + callsign, root); +} + +void Server::updateAobt(const std::string& callsign, const std::chrono::utc_clock::time_point& aobt) { + Json::Value root; + + root["callsign"] = callsign; + root["vacdm"] = Json::Value(); + root["vacdm"]["aobt"] = utils::Date::timestampToIsoString(aobt); + + this->sendPatchMessage("/api/v1/pilots/" + callsign, root); +} + +void Server::updateAort(const std::string& callsign, const std::chrono::utc_clock::time_point& aort) { + Json::Value root; + + root["callsign"] = callsign; + root["vacdm"] = Json::Value(); + root["vacdm"]["aort"] = utils::Date::timestampToIsoString(aort); + + this->sendPatchMessage("/api/v1/pilots/" + callsign, root); +} + +void Server::resetTobt(const std::string& callsign, const std::chrono::utc_clock::time_point& tobt, + const std::string& tobtState) { + Json::Value root; + + root["callsign"] = callsign; + root["vacdm"] = Json::Value(); + root["vacdm"]["tobt"] = utils::Date::timestampToIsoString(tobt); + root["vacdm"]["tobt_state"] = tobtState; + root["vacdm"]["tsat"] = utils::Date::timestampToIsoString(types::defaultTime); + root["vacdm"]["ttot"] = utils::Date::timestampToIsoString(types::defaultTime); + root["vacdm"]["asat"] = utils::Date::timestampToIsoString(types::defaultTime); + root["vacdm"]["asrt"] = utils::Date::timestampToIsoString(types::defaultTime); + root["vacdm"]["aobt"] = utils::Date::timestampToIsoString(types::defaultTime); + root["vacdm"]["atot"] = utils::Date::timestampToIsoString(types::defaultTime); + + sendPatchMessage("/api/v1/pilots/" + callsign, root); +} + +void Server::deletePilot(const std::string& callsign) { sendDeleteMessage("/api/v1/pilots/" + callsign); } + +void Server::setMaster(bool master) { this->m_clientIsMaster = master; } + +bool Server::getMaster() { return this->m_clientIsMaster; } + +const std::string& Server::errorMessage() const { return this->m_errorCode; } + +Server& Server::instance() { + static Server __instance; + return __instance; +} diff --git a/src/core/Server.h b/src/core/Server.h new file mode 100644 index 0000000..169824a --- /dev/null +++ b/src/core/Server.h @@ -0,0 +1,87 @@ +#pragma once + +#define CURL_STATICLIB 1 +#include +#include + +#include +#include +#include + +#include "types/Pilot.h" + +namespace vacdm::com { +class Server { + public: + typedef struct ServerConfiguration_t { + std::string name = ""; + bool allowMasterInSweatbox = false; + bool allowMasterAsObserver = false; + } ServerConfiguration; + + private: + Server(); + struct Communication { + std::mutex lock; + CURL* socket; + + Communication() : lock(), socket(curl_easy_init()) {} + }; + + std::string m_authToken; + Communication m_getRequest; + Communication m_postRequest; + Communication m_patchRequest; + Communication m_deleteRequest; + + bool m_apiIsChecked; + bool m_apiIsValid; + std::string m_baseUrl; + bool m_clientIsMaster; + std::string m_errorCode; + ServerConfiguration m_serverConfiguration; + + public: + ~Server(); + Server(const Server&) = delete; + Server(Server&&) = delete; + + Server& operator=(const Server&) = delete; + Server& operator=(Server&&) = delete; + + static Server& instance(); + + void changeServerAddress(const std::string& url); + bool checkWebApi(); + ServerConfiguration_t getServerConfig(); + std::list getPilots(const std::list airports); + void postPilot(types::Pilot); + void patchPilot(const Json::Value& root); + + /// @brief Sends a post message to the specififed endpoint url with the root as content + /// @param endpointUrl endpoint url to send the request to + /// @param root message content + void sendPostMessage(const std::string& endpointUrl, const Json::Value& root); + + /// @brief Sends a patch message to the specified endpoint url with the root as content + /// @param endpointUrl endpoint url to send the request to + /// @param root message content + void sendPatchMessage(const std::string& endpointUrl, const Json::Value& root); + void sendDeleteMessage(const std::string& endpointUrl); + + void updateExot(const std::string& pilot, const std::chrono::utc_clock::time_point& exot); + void updateTobt(const types::Pilot& pilot, const std::chrono::utc_clock::time_point& tobt, bool manualTobt); + void updateAsat(const std::string& callsign, const std::chrono::utc_clock::time_point& asat); + void updateAsrt(const std::string& callsign, const std::chrono::utc_clock::time_point& asrt); + void updateAobt(const std::string& callsign, const std::chrono::utc_clock::time_point& aobt); + void updateAort(const std::string& callsign, const std::chrono::utc_clock::time_point& aort); + + void resetTobt(const std::string& callsign, const std::chrono::utc_clock::time_point& tobt, + const std::string& tobtState); + void deletePilot(const std::string& callsign); + + const std::string& errorMessage() const; + void setMaster(bool master); + bool getMaster(); +}; +} // namespace vacdm::com \ No newline at end of file diff --git a/src/core/TagFunctions.h b/src/core/TagFunctions.h new file mode 100644 index 0000000..17ff580 --- /dev/null +++ b/src/core/TagFunctions.h @@ -0,0 +1,218 @@ +#pragma once + +#pragma warning(push, 0) +#include "EuroScopePlugIn.h" +#pragma warning(pop) + +#include "core/DataManager.h" +#include "core/Server.h" +#include "types/Pilot.h" +#include "utils/Date.h" +#include "utils/Number.h" + +using namespace vacdm; +using namespace vacdm::core; +using namespace vacdm::com; + +namespace vacdm::tagfunctions { + +enum itemFunction { + EXOT_MODIFY = 1, + EXOT_NEW_VALUE, + TOBT_NOW, + TOBT_MANUAL, + TOBT_MANUAL_EDIT, + TOBT_MENU, + ASAT_NOW, + ASAT_NOW_AND_STARTUP, + STARTUP_REQUEST, + TOBT_CONFIRM, + OFFBLOCK_REQUEST, + AOBT_NOW_AND_STATE, + RESET_TOBT, + RESET_ASAT, + RESET_ASRT, + RESET_TOBT_CONFIRM, + RESET_AORT, + RESET_AOBT_AND_STATE, + RESET_MENU, + RESET_PILOT, +}; + +void RegisterTagItemFuntions(vACDM *plugin) { + plugin->RegisterTagItemFunction("Modify EXOT", EXOT_MODIFY); + plugin->RegisterTagItemFunction("TOBT now", TOBT_NOW); + plugin->RegisterTagItemFunction("Set TOBT", TOBT_MANUAL); + plugin->RegisterTagItemFunction("TOBT confirm", TOBT_CONFIRM); + plugin->RegisterTagItemFunction("Tobt menu", TOBT_MENU); + plugin->RegisterTagItemFunction("ASAT now", ASAT_NOW); + plugin->RegisterTagItemFunction("ASAT now and startup state", ASAT_NOW_AND_STARTUP); + plugin->RegisterTagItemFunction("Startup Request", STARTUP_REQUEST); + plugin->RegisterTagItemFunction("Request Offblock", OFFBLOCK_REQUEST); + plugin->RegisterTagItemFunction("Set AOBT and Groundstate", AOBT_NOW_AND_STATE); + // Reset Functions + plugin->RegisterTagItemFunction("Reset TOBT", RESET_TOBT); + plugin->RegisterTagItemFunction("Reset ASAT", RESET_ASAT); + plugin->RegisterTagItemFunction("Reset confirmed TOBT", RESET_TOBT_CONFIRM); + plugin->RegisterTagItemFunction("Reset Offblock Request", RESET_AORT); + plugin->RegisterTagItemFunction("Reset AOBT", RESET_AOBT_AND_STATE); + plugin->RegisterTagItemFunction("Reset Menu", RESET_MENU); + plugin->RegisterTagItemFunction("Reset pilot", RESET_PILOT); +} + +void handleTagFunction(vACDM *plugin, int functionId, const char *itemString, POINT pt, RECT area) { + std::ignore = pt; + + // do not handle functions if client is not master + if (false == Server::instance().getMaster()) return; + + auto flightplan = plugin->FlightPlanSelectASEL(); + std::string callsign(flightplan.GetCallsign()); + + if (false == DataManager::instance().checkPilotExists(callsign)) return; + + auto pilot = DataManager::instance().getPilot(callsign); + + switch (static_cast(functionId)) { + case EXOT_MODIFY: + plugin->OpenPopupEdit(area, static_cast(itemFunction::EXOT_NEW_VALUE), itemString); + break; + case EXOT_NEW_VALUE: + if (true == isNumber(itemString)) { + const auto exot = std::chrono::utc_clock::time_point(std::chrono::minutes(std::atoi(itemString))); + if (exot != pilot.exot) + DataManager::instance().handleTagFunction(DataManager::MessageType::UpdateEXOT, pilot.callsign, + exot); + } + break; + case TOBT_NOW: + DataManager::instance().handleTagFunction(DataManager::MessageType::UpdateTOBT, pilot.callsign, + std::chrono::utc_clock::now()); + break; + case TOBT_MANUAL: + plugin->OpenPopupEdit(area, TOBT_MANUAL_EDIT, ""); + break; + case TOBT_MANUAL_EDIT: { + std::string clock(itemString); + if (clock.length() == 4 && isNumber(clock)) { + const auto hours = std::atoi(clock.substr(0, 2).c_str()); + const auto minutes = std::atoi(clock.substr(2, 4).c_str()); + if (hours >= 0 && hours < 24 && minutes >= 0 && minutes < 60) + DataManager::instance().handleTagFunction(DataManager::MessageType::UpdateTOBTConfirmed, + pilot.callsign, + utils::Date::convertStringToTimePoint(clock)); + else + plugin->DisplayMessage("Invalid time format. Expected: HHMM (24 hours)"); + } else if (clock.length() != 0) { + plugin->DisplayMessage("Invalid time format. Expected: HHMM (24 hours)"); + } + break; + } + case ASAT_NOW: { + DataManager::instance().handleTagFunction(DataManager::MessageType::UpdateASAT, pilot.callsign, + std::chrono::utc_clock::now()); + // if ASRT has not been set yet -> set ASRT + if (pilot.asrt == types::defaultTime) { + DataManager::instance().handleTagFunction(DataManager::MessageType::UpdateASRT, pilot.callsign, + std::chrono::utc_clock::now()); + } + break; + } + case ASAT_NOW_AND_STARTUP: { + DataManager::instance().handleTagFunction(DataManager::MessageType::UpdateASAT, pilot.callsign, + std::chrono::utc_clock::now()); + + // if ASRT has not been set yet -> set ASRT + if (pilot.asrt == types::defaultTime) { + DataManager::instance().handleTagFunction(DataManager::MessageType::UpdateASRT, pilot.callsign, + std::chrono::utc_clock::now()); + } + + plugin->SetGroundState(flightplan, "ST-UP"); + + break; + } + case STARTUP_REQUEST: { + DataManager::instance().handleTagFunction(DataManager::MessageType::UpdateASRT, pilot.callsign, + std::chrono::utc_clock::now()); + break; + } + case AOBT_NOW_AND_STATE: { + // set ASRT if ASRT has not been set yet + if (pilot.asrt == types::defaultTime) { + DataManager::instance().handleTagFunction(DataManager::MessageType::UpdateAORT, pilot.callsign, + std::chrono::utc_clock::now()); + } + DataManager::instance().handleTagFunction(DataManager::MessageType::UpdateAOBT, pilot.callsign, + std::chrono::utc_clock::now()); + + // set status depending on if the aircraft is positioned at a taxi-out position + if (pilot.taxizoneIsTaxiout) { + plugin->SetGroundState(flightplan, "TAXI"); + } else { + plugin->SetGroundState(flightplan, "PUSH"); + } + break; + } + case TOBT_CONFIRM: { + DataManager::instance().handleTagFunction(DataManager::MessageType::UpdateTOBTConfirmed, pilot.callsign, + pilot.tobt); + break; + } + case OFFBLOCK_REQUEST: { + DataManager::instance().handleTagFunction(DataManager::MessageType::UpdateAORT, pilot.callsign, + std::chrono::utc_clock::now()); + break; + } + case TOBT_MENU: { + plugin->OpenPopupList(area, "TOBT menu", 1); + plugin->AddPopupListElement("TOBT now", NULL, TOBT_NOW, false, 2, false, false); + plugin->AddPopupListElement("TOBT edit", NULL, TOBT_MANUAL, false, 2, false, false); + plugin->AddPopupListElement("TOBT confirm", NULL, TOBT_CONFIRM, false, 2, false, false); + break; + } + case RESET_TOBT: + DataManager::instance().handleTagFunction(DataManager::MessageType::ResetTOBT, pilot.callsign, + types::defaultTime); + break; + case RESET_ASAT: + DataManager::instance().handleTagFunction(DataManager::MessageType::ResetASAT, pilot.callsign, + types::defaultTime); + plugin->SetGroundState(flightplan, "NSTS"); + break; + case RESET_ASRT: + DataManager::instance().handleTagFunction(DataManager::MessageType::ResetASRT, pilot.callsign, + types::defaultTime); + break; + case RESET_TOBT_CONFIRM: + DataManager::instance().handleTagFunction(DataManager::MessageType::ResetTOBTConfirmed, pilot.callsign, + types::defaultTime); + break; + case RESET_AORT: + DataManager::instance().handleTagFunction(DataManager::MessageType::ResetAORT, pilot.callsign, + types::defaultTime); + break; + case RESET_AOBT_AND_STATE: + DataManager::instance().handleTagFunction(DataManager::MessageType::ResetAOBT, pilot.callsign, + types::defaultTime); + plugin->SetGroundState(flightplan, "NSTS"); + break; + case RESET_MENU: + plugin->OpenPopupList(area, "RESET menu", 1); + plugin->AddPopupListElement("Reset TOBT", NULL, RESET_TOBT, false, 2, false, false); + plugin->AddPopupListElement("Reset ASAT", NULL, RESET_ASAT, false, 2, false, false); + plugin->AddPopupListElement("Reset ASRT", NULL, RESET_ASRT, false, 2, false, false); + plugin->AddPopupListElement("Reset confirmed TOBT", NULL, RESET_TOBT_CONFIRM, false, 2, false, false); + plugin->AddPopupListElement("Reset AORT", NULL, RESET_AORT, false, 2, false, false); + plugin->AddPopupListElement("Reset AOBT", NULL, RESET_AOBT_AND_STATE, false, 2, false, false); + plugin->AddPopupListElement("Reset Pilot", NULL, RESET_PILOT, false, 2, false, false); + break; + case RESET_PILOT: + DataManager::instance().handleTagFunction(DataManager::MessageType::ResetPilot, pilot.callsign, + types::defaultTime); + break; + default: + break; + } +} +} // namespace vacdm::tagfunctions \ No newline at end of file diff --git a/src/core/TagItems.h b/src/core/TagItems.h new file mode 100644 index 0000000..47caa44 --- /dev/null +++ b/src/core/TagItems.h @@ -0,0 +1,136 @@ +#pragma once + +#include + +#include +#include +#include + +#include "TagItemsColor.h" +#include "types/Pilot.h" + +namespace vacdm::tagitems { +enum itemType { + EOBT, + TOBT, + TSAT, + TTOT, + EXOT, + ASAT, + AOBT, + ATOT, + ASRT, + AORT, + CTOT, + ECFMP_MEASURES, + EVENT_BOOKING, +}; + +void RegisterTagItemTypes(vACDM *plugin) { + plugin->RegisterTagItemType("EOBT", itemType::EOBT); + plugin->RegisterTagItemType("TOBT", itemType::TOBT); + plugin->RegisterTagItemType("TSAT", itemType::TSAT); + plugin->RegisterTagItemType("TTOT", itemType::TTOT); + plugin->RegisterTagItemType("EXOT", itemType::EXOT); + plugin->RegisterTagItemType("ASAT", itemType::ASAT); + plugin->RegisterTagItemType("AOBT", itemType::AOBT); + plugin->RegisterTagItemType("ATOT", itemType::ATOT); + plugin->RegisterTagItemType("ASRT", itemType::ASRT); + plugin->RegisterTagItemType("AORT", itemType::AORT); + plugin->RegisterTagItemType("CTOT", itemType::CTOT); + plugin->RegisterTagItemType("Event Booking", itemType::EVENT_BOOKING); + plugin->RegisterTagItemType("ECFMP Measures", itemType::ECFMP_MEASURES); +} + +std::string formatTime(const std::chrono::utc_clock::time_point timepoint) { + if (timepoint.time_since_epoch().count() > 0) + return std::format("{:%H%M}", timepoint); + else + return ""; +} + +void displayTagItem(EuroScopePlugIn::CFlightPlan FlightPlan, EuroScopePlugIn::CRadarTarget RadarTarget, int ItemCode, + int TagData, char sItemString[16], int *pColorCode, COLORREF *pRGB, double *pFontSize) { + std::ignore = RadarTarget; + std::ignore = TagData; + std::ignore = pRGB; + std::ignore = pFontSize; + + *pColorCode = EuroScopePlugIn::TAG_COLOR_RGB_DEFINED; + if (nullptr == FlightPlan.GetFlightPlanData().GetPlanType() || + 0 == std::strlen(FlightPlan.GetFlightPlanData().GetPlanType())) + return; + // skip non IFR flights + if (std::string_view("I") != FlightPlan.GetFlightPlanData().GetPlanType()) { + return; + } + std::string callsign = FlightPlan.GetCallsign(); + + if (false == DataManager::instance().checkPilotExists(callsign)) return; + + auto pilot = DataManager::instance().getPilot(callsign); + + std::stringstream outputText; + + switch (static_cast(ItemCode)) { + case itemType::EOBT: + outputText << formatTime(pilot.eobt); + *pRGB = Color::colorizeEobt(pilot); + break; + case itemType::TOBT: + outputText << formatTime(pilot.tobt); + *pRGB = Color::colorizeTobt(pilot); + break; + case itemType::TSAT: + outputText << formatTime(pilot.tsat); + *pRGB = Color::colorizeTsat(pilot); + break; + case itemType::TTOT: + outputText << formatTime(pilot.ttot); + *pRGB = Color::colorizeTtot(pilot); + break; + case itemType::EXOT: + if (pilot.exot.time_since_epoch().count() > 0) { + outputText << std::format("{:%M}", pilot.exot); + *pRGB = Color::colorizeExot(pilot); + } + break; + case itemType::ASAT: + outputText << formatTime(pilot.asat); + *pRGB = Color::colorizeAsat(pilot); + break; + case itemType::AOBT: + outputText << formatTime(pilot.aobt); + *pRGB = Color::colorizeAobt(pilot); + break; + case itemType::ATOT: + outputText << formatTime(pilot.atot); + *pRGB = Color::colorizeAtot(pilot); + break; + case itemType::ASRT: + outputText << formatTime(pilot.asrt); + *pRGB = Color::colorizeAsrt(pilot); + break; + case itemType::AORT: + outputText << formatTime(pilot.aort); + *pRGB = Color::colorizeAort(pilot); + break; + case itemType::CTOT: + outputText << formatTime(pilot.ctot); + *pRGB = Color::colorizeCtot(pilot); + break; + case itemType::ECFMP_MEASURES: + outputText << ""; + *pRGB = Color::colorizeEcfmpMeasure(pilot); + break; + case itemType::EVENT_BOOKING: + outputText << (pilot.hasBooking ? "B" : ""); + *pRGB = Color::colorizeEventBooking(pilot); + break; + default: + break; + } + + std::strcpy(sItemString, outputText.str().c_str()); +} +} // namespace vacdm::tagitems \ No newline at end of file diff --git a/src/core/TagItemsColor.h b/src/core/TagItemsColor.h new file mode 100644 index 0000000..e404f6b --- /dev/null +++ b/src/core/TagItemsColor.h @@ -0,0 +1,305 @@ +#pragma once + +#include "config/PluginConfig.h" +#include "types/Pilot.h" + +using namespace vacdm; + +namespace vacdm::tagitems { +class Color { + public: + Color() = delete; + Color(const Color &) = delete; + Color(Color &&) = delete; + Color &operator=(const Color &) = delete; + Color &operator=(Color &&) = delete; + + static inline PluginConfig pluginConfig; + static types::Pilot pilotT; + static void updatePluginConfig(PluginConfig newPluginConfig) { pluginConfig = newPluginConfig; } + + static COLORREF colorizeEobt(const types::Pilot &pilot) { return colorizeEobtAndTobt(pilot); } + + static COLORREF colorizeTobt(const types::Pilot &pilot) { return colorizeEobtAndTobt(pilot); } + + static COLORREF colorizeTsat(const types::Pilot &pilot) { + if (pilot.asat != types::defaultTime || pilot.tsat == types::defaultTime) { + return pluginConfig.grey; + } + const auto timeSinceTsat = + std::chrono::duration_cast(std::chrono::utc_clock::now() - pilot.tsat).count(); + if (timeSinceTsat <= 5 * 60 && timeSinceTsat >= -5 * 60) { + // CTOT exists + if (pilot.ctot.time_since_epoch().count() > 0) { + return pluginConfig.blue; + } + return pluginConfig.green; + } + // TSAT earlier than 5+ min + if (timeSinceTsat < -5 * 60) { + // CTOT exists + if (pilot.ctot.time_since_epoch().count() > 0) { + return pluginConfig.lightblue; + } + return pluginConfig.lightgreen; + } + // TSAT passed by 5+ min + if (timeSinceTsat > 5 * 60) { + // CTOT exists + if (pilot.ctot.time_since_epoch().count() > 0) { + return pluginConfig.red; + } + return pluginConfig.orange; + } + return pluginConfig.debug; + } + + static COLORREF colorizeTtot(const types::Pilot &pilot) { + if (pilot.ttot == types::defaultTime) { + return pluginConfig.grey; + } + + auto now = std::chrono::utc_clock::now(); + + // Round up to the next 10, 20, 30, 40, 50, or 00 minute interval + auto timeSinceEpoch = pilot.ttot.time_since_epoch(); + auto minutesSinceEpoch = std::chrono::duration_cast(timeSinceEpoch); + std::chrono::time_point rounded; + + // Compute the number of minutes remaining to the next highest ten + auto remainingMinutes = 10 - minutesSinceEpoch.count() % 10; + + // If the time point is already at a multiple of ten minutes, no rounding is needed + if (remainingMinutes == 10) { + rounded = std::chrono::time_point_cast(pilot.ttot); + } else { + // Add the remaining minutes to the time point + auto roundedUpMinutes = minutesSinceEpoch + std::chrono::minutes(remainingMinutes); + + // Convert back to a time_point object and return + rounded = std::chrono::time_point_cast( + std::chrono::utc_clock::time_point(roundedUpMinutes)); + rounded += std::chrono::seconds(30); + } + + // Check if the current time has passed the ttot time point + if (pilot.atot.time_since_epoch().count() > 0) { + // ATOT exists + return pluginConfig.grey; + } + if (now < rounded) { + // time before TTOT and during TTOT block + return pluginConfig.green; + } else if (now >= rounded) { + // time past TTOT / TTOT block + return pluginConfig.orange; + } + return pluginConfig.debug; + } + + static COLORREF colorizeExot(const types::Pilot &pilot) { + std::ignore = pilot; + return EuroScopePlugIn::TAG_COLOR_DEFAULT; + } + + static COLORREF colorizeAsat(const types::Pilot &pilot) { + if (pilot.asat == types::defaultTime) { + return pluginConfig.grey; + } + + if (pilot.aobt.time_since_epoch().count() > 0) { + return pluginConfig.grey; + } + + const auto timeSinceAsat = + std::chrono::duration_cast(std::chrono::utc_clock::now() - pilot.asat).count(); + const auto timeSinceTsat = + std::chrono::duration_cast(std::chrono::utc_clock::now() - pilot.tsat).count(); + if (pilot.taxizoneIsTaxiout == false) { + if (/* Datalink clearance == true &&*/ timeSinceTsat >= -5 * 60 && timeSinceTsat <= 5 * 60) { + return pluginConfig.green; + } + if (timeSinceAsat < 5 * 60) { + return pluginConfig.green; + } + } + if (pilot.taxizoneIsTaxiout == true) { + if (timeSinceTsat >= -5 * 60 && timeSinceTsat <= 10 * 60 /* && Datalink clearance == true*/) { + return pluginConfig.green; + } + if (timeSinceAsat < 10 * 60) { + return pluginConfig.green; + } + } + return pluginConfig.orange; + } + + static COLORREF colorizeAobt(const types::Pilot &pilot) { + std::ignore = pilot; + return pluginConfig.grey; + } + + static COLORREF colorizeAtot(const types::Pilot &pilot) { + std::ignore = pilot; + return pluginConfig.grey; + } + + static COLORREF colorizeAsrt(const types::Pilot &pilot) { + if (pilot.asat.time_since_epoch().count() > 0) { + return pluginConfig.grey; + } + const auto timeSinceAsrt = + std::chrono::duration_cast(std::chrono::utc_clock::now() - pilot.asrt).count(); + if (timeSinceAsrt <= 5 * 60 && timeSinceAsrt >= 0) { + return pluginConfig.green; + } + if (timeSinceAsrt > 5 * 60 && timeSinceAsrt <= 10 * 60) { + return pluginConfig.yellow; + } + if (timeSinceAsrt > 10 * 60 && timeSinceAsrt <= 15 * 60) { + return pluginConfig.orange; + } + if (timeSinceAsrt > 15 * 60) { + return pluginConfig.red; + } + + return pluginConfig.debug; + } + + static COLORREF colorizeAort(const types::Pilot &pilot) { + if (pilot.aort == types::defaultTime) { + return pluginConfig.grey; + } + if (pilot.aobt.time_since_epoch().count() > 0) { + return pluginConfig.grey; + } + const auto timeSinceAort = + std::chrono::duration_cast(std::chrono::utc_clock::now() - pilot.aort).count(); + + if (timeSinceAort <= 5 * 60 && timeSinceAort >= 0) { + return pluginConfig.green; + } + if (timeSinceAort > 5 * 60 && timeSinceAort <= 10 * 60) { + return pluginConfig.yellow; + } + if (timeSinceAort > 10 * 60 && timeSinceAort <= 15 * 60) { + return pluginConfig.orange; + } + if (timeSinceAort > 15 * 60) { + return pluginConfig.red; + } + + return pluginConfig.debug; + } + + static COLORREF colorizeCtot(const types::Pilot &pilot) { return colorizeCtotandCtottimer(pilot); } + + static COLORREF colorizeCtotTimer(const types::Pilot &pilot) { return colorizeCtotandCtottimer(pilot); } + + static COLORREF colorizeAsatTimer(const types::Pilot &pilot) { + // aort set + if (pilot.aort.time_since_epoch().count() > 0) { + return pluginConfig.grey; + } + const auto timeSinceAobt = + std::chrono::duration_cast(std::chrono::utc_clock::now() - pilot.aobt).count(); + if (timeSinceAobt >= 0) { + // hide Timer + } + const auto timeSinceAsat = + std::chrono::duration_cast(std::chrono::utc_clock::now() - pilot.asat).count(); + const auto timeSinceTsat = + std::chrono::duration_cast(std::chrono::utc_clock::now() - pilot.tsat).count(); + // Pushback required + if (pilot.taxizoneIsTaxiout != false) { + /* + if (hasdatalinkclearance == true && timesincetsat >=5*60 && timesincetsat <=5*60) + { + return pluginConfig.green + } */ + if (timeSinceAsat < 5 * 60) { + return pluginConfig.green; + } + } + if (pilot.taxizoneIsTaxiout == true) { + if (timeSinceTsat >= -5 * 60 && timeSinceTsat <= 10 * 60) { + return pluginConfig.green; + } + if (timeSinceAsat <= 10 * 60) { + return pluginConfig.green; + } + } + return pluginConfig.orange; + } + + // other: + + static COLORREF colorizeEcfmpMeasure(const types::Pilot &pilot) { + return pilot.measures.empty() ? pluginConfig.grey : pluginConfig.green; + } + + static COLORREF colorizeEventBooking(const types::Pilot &pilot) { + return pilot.hasBooking ? pluginConfig.green : pluginConfig.grey; + } + + private: + static COLORREF colorizeEobtAndTobt(const types::Pilot &pilot) { + const auto now = std::chrono::utc_clock::now(); + const auto timeSinceTobt = std::chrono::duration_cast(now - pilot.tobt).count(); + const auto timeSinceTsat = std::chrono::duration_cast(now - pilot.tsat).count(); + const auto diffTsatTobt = std::chrono::duration_cast(pilot.tsat - pilot.tobt).count(); + + if (pilot.tsat == types::defaultTime) { + return pluginConfig.grey; + } + // ASAT exists + if (pilot.asat.time_since_epoch().count() > 0) { + return pluginConfig.grey; + } + // TOBT in past && TSAT expired, i.e. 5min past TSAT || TOBT >= +1h || TSAT does not exist && TOBT in past + // -> TOBT in past && (TSAT expired || TSAT does not exist) || TOBT >= now + 1h + if (timeSinceTobt > 0 && (timeSinceTsat >= 5 * 60 || pilot.tsat == types::defaultTime) || + pilot.tobt >= now + std::chrono::hours(1)) // last statement could cause problems + { + return pluginConfig.orange; + } + // Diff TOBT TSAT >= 5min && unconfirmed + if (diffTsatTobt >= 5 * 60 && (pilot.tobt_state == "GUESS" || pilot.tobt_state == "FLIGHTPLAN")) { + return pluginConfig.lightyellow; + } + // Diff TOBT TSAT >= 5min && confirmed + if (diffTsatTobt >= 5 * 60 && pilot.tobt_state == "CONFIRMED") { + return pluginConfig.yellow; + } + // Diff TOBT TSAT < 5min + if (diffTsatTobt < 5 * 60 && pilot.tobt_state == "CONFIRMED") { + return pluginConfig.green; + } + // tobt is not confirmed + if (pilot.tobt_state != "CONFIRMED") { + return pluginConfig.lightgreen; + } + return pluginConfig.debug; + } + + static COLORREF colorizeCtotandCtottimer(const types::Pilot &pilot) { + if (pilot.ctot == types::defaultTime) { + return pluginConfig.grey; + } + + const auto timetoctot = + std::chrono::duration_cast(std::chrono::utc_clock::now() - pilot.ctot).count(); + if (timetoctot >= 5 * 60) { + return pluginConfig.lightgreen; + } + if (timetoctot <= 5 * 60 && timetoctot >= -10 * 60) { + return pluginConfig.green; + } + if (timetoctot < -10 * 60) { + return pluginConfig.orange; + } + + return pluginConfig.grey; + } +}; +} // namespace vacdm::tagitems \ No newline at end of file diff --git a/src/log/Logger.cpp b/src/log/Logger.cpp new file mode 100644 index 0000000..4dfb3bd --- /dev/null +++ b/src/log/Logger.cpp @@ -0,0 +1,204 @@ +#include "Logger.h" + +#ifdef DEBUG_BUILD +#include + +#include +#endif + +#include +#include +#include + +#include "utils/String.h" + +using namespace std::chrono_literals; +using namespace vacdm::logging; + +static const char __loggingTable[] = + "CREATE TABLE messages( \ + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, \ + sender TEXT, \ + level INT, \ + message TEXT \ +);"; +static const std::string __insertMessage = "INSERT INTO messages VALUES (CURRENT_TIMESTAMP, @1, @2, @3)"; + +Logger::Logger() { + stream << std::format("{0:%Y%m%d%H%M%S}", std::chrono::utc_clock::now()) << ".vacdm"; +#ifdef DEBUG_BUILD + AllocConsole(); +#pragma warning(push) +#pragma warning(disable : 6031) + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); +#pragma warning(pop) + this->enableLogging(); +#endif + this->m_logWriter = std::thread(&Logger::run, this); +} + +Logger::~Logger() { + this->m_stop = true; + this->m_logWriter.join(); + + if (nullptr != this->m_database) sqlite3_close_v2(this->m_database); +} + +void Logger::run() { + while (true) { + if (m_stop) return; + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + // obtain a copy of the logs, clear the log list to minimize lock time + this->m_logLock.lock(); + auto logs = m_asynchronousLogs; + m_asynchronousLogs.clear(); + this->m_logLock.unlock(); + + auto it = logs.begin(); + while (it != logs.end()) { + auto logsetting = std::find_if(logSettings.begin(), logSettings.end(), + [it](const LogSetting &setting) { return setting.sender == it->sender; }); + + if (logsetting != logSettings.end() && it->loglevel >= logsetting->minimumLevel && + false == this->m_LogAll) { +#ifdef DEBUG_BUILD + std::cout << logsetting->name << ": " << it->message << "\n"; +#endif + + sqlite3_stmt *stmt; + + sqlite3_prepare_v2(this->m_database, __insertMessage.c_str(), __insertMessage.length(), &stmt, nullptr); + sqlite3_bind_text(stmt, 1, logsetting->name.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 2, static_cast(it->loglevel)); + sqlite3_bind_text(stmt, 3, it->message.c_str(), -1, SQLITE_TRANSIENT); + + sqlite3_step(stmt); + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); + } + it = logs.erase(it); + } + } +} + +void Logger::log(const LogSender &sender, const std::string &message, const LogLevel loglevel) { + std::lock_guard guard(this->m_logLock); + if (true == this->loggingEnabled) m_asynchronousLogs.push_back({sender, message, loglevel}); +} + +std::string Logger::handleLogCommand(std::string command) { + auto elements = vacdm::utils::String::splitString(command, " "); + + std::string usageString = "Usage: .vacdm LOG ON/OFF/DEBUG"; + if (elements.size() != 3) return usageString; + + if ("ON" == elements[2]) { + this->enableLogging(); + return "Enabled logging"; + } else if ("OFF" == elements[2]) { + this->disableLogging(); + return "Disabled logging"; + } else if ("DEBUG" == elements[2]) { + std::lock_guard guard(this->m_logLock); + if (false == this->m_LogAll) { + this->m_LogAll = true; + return "Set all log levels to DEBUG"; + } else { + this->m_LogAll = false; + return "Reset log levels, using previous settings"; + } + } + + return usageString; +} + +std::string Logger::handleLogLevelCommand(std::string command) { + const auto elements = vacdm::utils::String::splitString(command, " "); + if (elements.size() != 4) { + return "Usage: .vacdm LOGLEVEL sender loglevel"; + } + + std::string sender = elements[2]; + std::string newLevel = elements[3]; + + std::lock_guard guard(this->m_logLock); + auto logsetting = std::find_if(logSettings.begin(), logSettings.end(), [sender](const LogSetting &setting) { + std::string uppercaseName = setting.name; +#pragma warning(push) +#pragma warning(disable : 4244) + std::transform(uppercaseName.begin(), uppercaseName.end(), uppercaseName.begin(), ::toupper); +#pragma warning(pop) + return uppercaseName == sender; + }); + + // sender not found + if (logsetting == logSettings.end()) { + return "Sender " + sender + " not found. Available senders are " + + std::accumulate(std::next(logSettings.begin()), logSettings.end(), logSettings.front().name, + [](std::string acc, const LogSetting &setting) { return acc + " " + setting.name; }); + } + + // Modify logsetting by reference + auto &logSettingRef = *logsetting; + +#pragma warning(push) +#pragma warning(disable : 4244) + std::transform(newLevel.begin(), newLevel.end(), newLevel.begin(), ::toupper); +#pragma warning(pop) + + if (newLevel == "DEBUG") { + logSettingRef.minimumLevel = LogLevel::Debug; + } else if (newLevel == "INFO") { + logSettingRef.minimumLevel = LogLevel::Info; + } else if (newLevel == "WARNING") { + logSettingRef.minimumLevel = LogLevel::Warning; + } else if (newLevel == "ERROR") { + logSettingRef.minimumLevel = LogLevel::Error; + } else if (newLevel == "CRITICAL") { + logSettingRef.minimumLevel = LogLevel::Critical; + } else if (newLevel == "SYSTEM") { + logSettingRef.minimumLevel = LogLevel::System; + } else if (newLevel == "DISABLED") { + logSettingRef.minimumLevel = LogLevel::Disabled; + } else { + return "Invalid log level: " + newLevel; + } + + // check if at least one sender is set to log + bool enableLogging = false; + for (auto logSetting : logSettings) { + if (logsetting->minimumLevel != LogLevel::Disabled) { + enableLogging = true; + break; + } + } + this->loggingEnabled = enableLogging; + + return "Changed sender " + sender + " to " + newLevel; +} + +void Logger::enableLogging() { + if (false == this->logFileCreated) createLogFile(); + + std::lock_guard guard(this->m_logLock); + this->loggingEnabled = true; +} + +void Logger::disableLogging() { + std::lock_guard guard(this->m_logLock); + this->loggingEnabled = false; +} + +void Logger::createLogFile() { + sqlite3_open(stream.str().c_str(), &this->m_database); + sqlite3_exec(this->m_database, __loggingTable, nullptr, nullptr, nullptr); + sqlite3_exec(this->m_database, "PRAGMA journal_mode = MEMORY", nullptr, nullptr, nullptr); + logFileCreated = true; +} + +Logger &Logger::instance() { + static Logger __instance; + return __instance; +} diff --git a/src/log/Logger.h b/src/log/Logger.h new file mode 100644 index 0000000..3dc7134 --- /dev/null +++ b/src/log/Logger.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "sqlite3.h" + +namespace vacdm::logging { +class Logger { + public: + enum LogSender { + vACDM, + DataManager, + Server, + ConfigParser, + Utils, + }; + + enum LogLevel { + Debug, + Info, + Warning, + Error, + Critical, + System, + Disabled, + }; + + struct LogSetting { + LogSender sender; + std::string name; + LogLevel minimumLevel; + }; + + struct AsynchronousLog { + LogSender sender; + std::string message; + LogLevel loglevel; + }; + + private: + Logger(); +#ifdef DEBUG_BUILD + std::vector logSettings = { + {vACDM, "vACDM", Debug}, {DataManager, "DataManager", Info}, + {Server, "Server", Debug}, {ConfigParser, "ConfigParser", Debug}, + {Utils, "Utils", Debug}, + }; +#else + /// @brief set the log level for each sender separately + std::vector logSettings = { + {vACDM, "vACDM", Disabled}, {DataManager, "DataManager", Disabled}, + {Server, "Server", Disabled}, {ConfigParser, "ConfigParser", Disabled}, + {Utils, "Utils", Disabled}, + }; +#endif + bool m_LogAll = false; + + std::mutex m_logLock; + std::list m_asynchronousLogs; + std::thread m_logWriter; + bool m_stop = false; + void run(); + + void enableLogging(); + void disableLogging(); + bool loggingEnabled = false; + + sqlite3 *m_database; + std::stringstream stream; + bool logFileCreated = false; + void createLogFile(); + + public: + ~Logger(); + /// @brief queues a log message to be processed asynchronously + /// @param sender the sender (e.g. class) + /// @param message the message to be displayed + /// @param loglevel the severity, must be greater than m_minimumLogLevel to be logged + void log(const LogSender &sender, const std::string &message, const LogLevel loglevel); + std::string handleLogCommand(std::string command); + std::string handleLogLevelCommand(std::string command); + static Logger &instance(); +}; +} // namespace vacdm::logging \ No newline at end of file diff --git a/logging/sqlite3.c b/src/log/sqlite3.c similarity index 100% rename from logging/sqlite3.c rename to src/log/sqlite3.c diff --git a/logging/sqlite3.h b/src/log/sqlite3.h similarity index 100% rename from logging/sqlite3.h rename to src/log/sqlite3.h diff --git a/logging/sqlite3ext.h b/src/log/sqlite3ext.h similarity index 100% rename from logging/sqlite3ext.h rename to src/log/sqlite3ext.h diff --git a/main.cpp b/src/main.cpp similarity index 57% rename from main.cpp rename to src/main.cpp index cd0eb2d..01a82cb 100644 --- a/main.cpp +++ b/src/main.cpp @@ -1,18 +1,15 @@ -#include -#pragma warning(push, 0) -#include -#pragma warning(pop) - -#include "vACDM.h" - -std::unique_ptr Plugin; - -void __declspec (dllexport) EuroScopePlugInInit(EuroScopePlugIn::CPlugIn **ppPlugInInstance) -{ - Plugin.reset(new vacdm::vACDM()); - *ppPlugInInstance = Plugin.get(); -} - -void __declspec (dllexport) EuroScopePlugInExit(void) -{ -} +#include +#pragma warning(push, 0) +#include +#pragma warning(pop) + +#include "vACDM.h" + +std::unique_ptr Plugin; + +void __declspec(dllexport) EuroScopePlugInInit(EuroScopePlugIn::CPlugIn **ppPlugInInstance) { + Plugin.reset(new vacdm::vACDM()); + *ppPlugInInstance = Plugin.get(); +} + +void __declspec(dllexport) EuroScopePlugInExit(void) {} diff --git a/src/types/Ecfmp.h b/src/types/Ecfmp.h new file mode 100644 index 0000000..03f2dbc --- /dev/null +++ b/src/types/Ecfmp.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +namespace vacdm::types { +// defines the types returned by the ECFMP API +// see https://ecfmp.vatsim.net/docs/v1 for documentation + +typedef struct EcfmpMeasure_t { + std::string ident; + std::int64_t value = -1; + std::vector mandatoryRoute; +} EcfmpMeasure; + +typedef struct EcfmpFilter_t { + std::string type; + std::vector value; + std::vector flightlevels; + std::vector waypoints; +} EcfmpFilter; + +typedef struct EcfmpFlowMeasure_t { + std::int64_t number; + std::string ident; + std::int64_t event_id; + std::string reason; + std::chrono::utc_clock::time_point starttime; + std::chrono::utc_clock::time_point endtime; + std::chrono::utc_clock::time_point withdrawn_at; + std::vector notified_fir_regions; + std::vector measures; + std::vector filters; +} EcfmpFlowMeasure; +} // namespace vacdm::types \ No newline at end of file diff --git a/types/Flight.h b/src/types/Pilot.h similarity index 62% rename from types/Flight.h rename to src/types/Pilot.h index d76885d..5e9571f 100644 --- a/types/Flight.h +++ b/src/types/Pilot.h @@ -1,62 +1,54 @@ -#pragma once - -#include -#include - -namespace vacdm { -namespace types { - -static constexpr std::chrono::utc_clock::time_point defaultTime = std::chrono::utc_clock::time_point(std::chrono::milliseconds(-1)); - -typedef struct Measure { - std::string ident; // measure id - int value = -1; // measure value in seconds, i.e. 5 -} Measure; - -typedef struct Flight { - std::chrono::utc_clock::time_point lastUpdate; - std::string callsign; - bool inactive = false; - - // position/* - double latitude = 0.0; - double longitude = 0.0; - bool taxizoneIsTaxiout = false; - - // flightplan/* - std::string origin; - std::string destination; - std::string rule; - - // vacdm/* - std::chrono::utc_clock::time_point eobt = defaultTime; - std::chrono::utc_clock::time_point tobt = defaultTime; - std::chrono::utc_clock::time_point ctot = defaultTime; - std::chrono::utc_clock::time_point ttot = defaultTime; - std::chrono::utc_clock::time_point tsat = defaultTime; - std::chrono::utc_clock::time_point exot = defaultTime; - std::chrono::utc_clock::time_point asat = defaultTime; - std::chrono::utc_clock::time_point aobt = defaultTime; - std::chrono::utc_clock::time_point atot = defaultTime; - std::chrono::utc_clock::time_point asrt = defaultTime; - std::chrono::utc_clock::time_point aort = defaultTime; - std::string tobt_state = ""; - - // booking/* - bool hasBooking = false; - - // clearance/* - std::string runway; - std::string sid; - std::string assignedSquawk; - std::string currentSquawk; - std::string initialClimb; - - bool departed = false; - - // ecfmp measures/* - std::vector measures; -} Flight_t; - -} -} +#pragma once + +#include +#include + +#include "Ecfmp.h" + +namespace vacdm::types { +static constexpr std::chrono::utc_clock::time_point defaultTime = + std::chrono::utc_clock::time_point(std::chrono::milliseconds(-1)); + +typedef struct Pilot_t { + std::string callsign; + std::chrono::utc_clock::time_point lastUpdate; + + bool inactive = false; + + // position data + + double latitude = 0.0; + double longitude = 0.0; + bool taxizoneIsTaxiout = false; + + // flightplan & clearance data + + std::string origin; + std::string destination; + std::string runway; + std::string sid; + + // ACDM procedure data + + std::chrono::utc_clock::time_point eobt = defaultTime; + std::chrono::utc_clock::time_point tobt = defaultTime; + std::string tobt_state; + std::chrono::utc_clock::time_point ctot = defaultTime; + std::chrono::utc_clock::time_point ttot = defaultTime; + std::chrono::utc_clock::time_point tsat = defaultTime; + std::chrono::utc_clock::time_point exot = defaultTime; + std::chrono::utc_clock::time_point asat = defaultTime; + std::chrono::utc_clock::time_point aobt = defaultTime; + std::chrono::utc_clock::time_point atot = defaultTime; + std::chrono::utc_clock::time_point asrt = defaultTime; + std::chrono::utc_clock::time_point aort = defaultTime; + + // ECFMP Measures + + std::vector measures; + + // event booking data + + bool hasBooking = false; +} Pilot; +} // namespace vacdm::types \ No newline at end of file diff --git a/src/utils/Date.h b/src/utils/Date.h new file mode 100644 index 0000000..c675c08 --- /dev/null +++ b/src/utils/Date.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include + +#pragma warning(push, 0) +#include "EuroScopePlugIn.h" +#pragma warning(pop) + +#include "log/Logger.h" + +namespace vacdm::utils { + +class Date { + public: + Date() = delete; + Date(const Date &) = delete; + Date(Date &&) = delete; + Date &operator=(const Date &) = delete; + Date &operator=(Date &&) = delete; + + /// @brief Converts std::chrono::utc_clock::time_point to an ISO-formatted string. + /// + /// This function takes a std::chrono::utc_clock::time_point and converts it to an + /// ISO-formatted string. The resulting string follows the format "%Y-%m-%dT%H:%M:%S.Z". + /// The function ensures proper formatting, including the addition of "Z" to indicate UTC. + /// + /// If the provided time_point is non-negative (i.e., not before the epoch), the + /// function formats it into a string and appends "Z" to indicate UTC. If the time_point + /// is negative, the function returns a default timestamp representing "1969-12-31T23:59:59.999Z". + /// + /// @param timepoint std::chrono::utc_clock::time_point to be converted. + /// @return ISO-formatted string representing the converted timestamp. + static std::string timestampToIsoString(const std::chrono::utc_clock::time_point &timepoint) { + if (timepoint.time_since_epoch().count() >= 0) { + std::stringstream stream; + stream << std::format("{0:%FT%T}", timepoint); + auto timestamp = stream.str(); + timestamp = timestamp.substr(0, timestamp.length() - 4) + "Z"; + return timestamp; + } else { + return "1969-12-31T23:59:59.999Z"; + } + } + + /// @brief Converts an ISO-formatted string to std::chrono::utc_clock::time_point. + /// + /// This function takes an ISO-formatted string representing a timestamp and converts + /// it to a std::chrono::utc_clock::time_point. The input string is expected to be in + /// the format "%Y-%m-%dT%H:%M:%S". + /// + /// The function uses a std::stringstream to parse the input string and create a + /// std::chrono::utc_clock::time_point. The resulting time_point is then returned. + /// + /// @param timestamp ISO-formatted string representing the timestamp. + /// @return std::chrono::utc_clock::time_point representing the converted timestamp. + static std::chrono::utc_clock::time_point isoStringToTimestamp(const std::string ×tamp) { + std::chrono::utc_clock::time_point retval; + std::stringstream stream; + + stream << timestamp.substr(0, timestamp.length() - 1); + std::chrono::from_stream(stream, "%FT%T", retval); + + return retval; + } + + /// @brief Converts a EuroScope departure time string to a UTC time_point. + /// This function takes a EuroScope flight plan and extracts the estimated departure time string. + /// Using a different util function it then convert the string to a utc time_point + /// + /// @param flightplan EuroScope flight plan to extract information from. + /// @return std::chrono::utc_clock::time_point representing the converted departure time. + /// + static std::chrono::utc_clock::time_point convertEuroscopeDepartureTime( + const EuroScopePlugIn::CFlightPlan flightplan) { + const std::string callsign = flightplan.GetCallsign(); + const std::string eobt = flightplan.GetFlightPlanData().GetEstimatedDepartureTime(); + + return convertStringToTimePoint(eobt); + } + /// @brief Converts a 4-character HHMM string to a UTC time_point. + /// This function takes a 4-character string representing time in HHMM format. + /// If the string is not valid (empty or exceeds 4 characters), the function returns the + /// current UTC time. Otherwise, it constructs a formatted string combining the current + /// date (year, month, day) with the given time, ensuring proper zero-padding. + /// The formatted string is then parsed to obtain a std::chrono::utc_clock::time_point. + /// + /// @param hhmmString The 4-character string representing time in HHMM format. + /// @return std::chrono::utc_clock::time_point representing the converted time. + /// + static std::chrono::utc_clock::time_point convertStringToTimePoint(const std::string &hhmmString) { + const auto now = std::chrono::utc_clock::now(); + if (hhmmString.length() == 0 || hhmmString.length() > 4) { + return now; + } + + std::stringstream stream; + stream << std::format("{0:%Y%m%d}", now); + std::size_t requiredLeadingZeros = 4 - hhmmString.length(); + while (requiredLeadingZeros != 0) { + requiredLeadingZeros -= 1; + stream << "0"; + } + stream << hhmmString; + + std::chrono::utc_clock::time_point time; + std::stringstream input(stream.str()); + std::chrono::from_stream(stream, "%Y%m%d%H%M", time); + + return time; + } +}; +} // namespace vacdm::utils diff --git a/src/utils/Number.h b/src/utils/Number.h new file mode 100644 index 0000000..d5efb94 --- /dev/null +++ b/src/utils/Number.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace vacdm { +static __inline bool isNumber(const std::string& s) { + return !s.empty() && std::find_if(s.begin(), s.end(), [](unsigned char c) { return !std::isdigit(c); }) == s.end(); +} +} // namespace vacdm \ No newline at end of file diff --git a/src/utils/String.h b/src/utils/String.h new file mode 100644 index 0000000..e154de9 --- /dev/null +++ b/src/utils/String.h @@ -0,0 +1,113 @@ +/* + * @brief Defines and implements functions to handle strings + * @file helper/String.h + * @author Sven Czarnian + * @copyright Copyright 2020-2021 Sven Czarnian + * @license This project is published under the GNU General Public License v3 + * (GPLv3) + */ + +#pragma once + +#include +#include +#include + +namespace vacdm::utils { +/** + * @brief Implements and defines convenience functions for the string handling + * @ingroup helper + */ +class String { + private: + template + static auto splitAux(const std::string &value, Separator &&separator) -> std::vector { + std::vector result; + std::string::size_type p = 0; + std::string::size_type q; + while ((q = separator(value, p)) != std::string::npos) { + result.emplace_back(value, p, q - p); + p = q + 1; + } + result.emplace_back(value, p); + return result; + } + + public: + String() = delete; + String(const String &) = delete; + String(String &&) = delete; + String &operator=(const String &) = delete; + String &operator=(String &&) = delete; + + /** + * @brief Replaces all markers by replace in message + * @param[in,out] message The message which needs to be modified + * @param[in] marker The wildcard which needs to be found in message and which + * needs to be replaced + * @param[in] replace The replacement of marker in message + * @return + */ + static __inline void stringReplace(std::string &message, const std::string &marker, const std::string &replace) { + std::size_t pos = message.find(marker, 0); + while (std::string::npos != pos) { + auto it = message.cbegin() + pos; + message.replace(it, it + marker.length(), replace); + pos = message.find(marker, pos + marker.length()); + } + } + + /** + * @brief Splits value into chunks and the separator is defined in separators + * @param[in] value The string which needs to be splitted up + * @param[in] separators The separators which split up the value + * @return The list of splitted chunks + */ + static auto splitString(const std::string &value, const std::string &separators) -> std::vector { + return String::splitAux(value, [&](const std::string &v, std::string::size_type p) noexcept { + return v.find_first_of(separators, p); + }); + } + + /** + * @brief Removes leading and trailing whitespaces + * @param[in] value The trimmable string + * @param[in] spaces The characters that need to be removed + * @return The trimmed version of value + */ + static auto trim(const std::string &value, const std::string &spaces = " \t") -> std::string { + const auto begin = value.find_first_not_of(spaces, 0); + if (std::string::npos == begin) return ""; + + const auto end = value.find_last_not_of(spaces); + const auto range = end - begin + 1; + + return value.substr(begin, range); + } + + /** + * @brief finds the ICAO in a EuroScope airport name + * @details There is no consistent naming defined for EuroScope airports + * This means that an airport name may include only the ICAO (e.g. "EDDK") or additionally the name aswell like + * "EDDK Cologne-Bonn" in no paticular order. This functions finds the ICAO in this string based on two conditions. + * 1. The airport ICAO is 4-letters long + * 2. The airport ICAO is full uppercase + * @param[in] input the string to find the ICAO in + * @return the ICAO or "" if none was found + */ + static auto findIcao(std::string input) -> std::string { + if (input.size() < 4) return ""; + + // split the input into separate words + const auto words = splitString(input, " "); + + for (auto word : words) { + if (word.size() == 4 && std::all_of(word.begin(), word.end(), [](char c) { return std::isupper(c); })) { + return word; + } + } + + return ""; // Return an empty string if no valid ICAO code is found + } +}; +} // namespace vacdm::utils diff --git a/src/vACDM.cpp b/src/vACDM.cpp new file mode 100644 index 0000000..a89fee4 --- /dev/null +++ b/src/vACDM.cpp @@ -0,0 +1,263 @@ +#include "vACDM.h" + +#include +#include + +#include + +#include "Version.h" +#include "config/ConfigParser.h" +#include "core/DataManager.h" +#include "core/Server.h" +#include "core/TagFunctions.h" +#include "core/TagItems.h" +#include "log/Logger.h" +#include "utils/Date.h" +#include "utils/Number.h" +#include "utils/String.h" + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +using namespace vacdm; +using namespace vacdm::com; +using namespace vacdm::core; +using namespace vacdm::tagitems; +using namespace vacdm::logging; +using namespace vacdm::utils; + +namespace vacdm { +vACDM::vACDM() + : CPlugIn(EuroScopePlugIn::COMPATIBILITY_CODE, PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR, PLUGIN_LICENSE) { + DisplayMessage("Version " + std::string(PLUGIN_VERSION) + " loaded", "Initialisation"); + Logger::instance().log(Logger::LogSender::vACDM, "Version " + std::string(PLUGIN_VERSION) + " loaded", + Logger::LogLevel::System); + + if (0 != curl_global_init(CURL_GLOBAL_ALL)) DisplayMessage("Unable to initialize the network stack!"); + + // get the dll-path + char path[MAX_PATH + 1] = {0}; + GetModuleFileNameA((HINSTANCE)&__ImageBase, path, MAX_PATH); + PathRemoveFileSpecA(path); + this->m_dllPath = std::string(path); + + tagitems::RegisterTagItemTypes(this); + tagfunctions::RegisterTagItemFuntions(this); + + this->reloadConfiguration(true); +} + +vACDM::~vACDM() {} + +void vACDM::DisplayMessage(const std::string &message, const std::string &sender) { + DisplayUserMessage("vACDM", sender.c_str(), message.c_str(), true, false, false, false, false); +} + +void vACDM::checkServerConfiguration() { + if (Server::instance().checkWebApi() == false) { + DisplayMessage("Connection failed.", "Server"); + DisplayMessage(Server::instance().errorMessage().c_str(), "Server"); + } else { + std::string serverName = Server::instance().getServerConfig().name; + DisplayMessage(("Connected to " + serverName), "Server"); + // set active airports and runways + this->OnAirportRunwayActivityChanged(); + } +} + +void vACDM::runEuroscopeUpdate() { + for (EuroScopePlugIn::CFlightPlan flightplan = FlightPlanSelectFirst(); flightplan.IsValid(); + flightplan = FlightPlanSelectNext(flightplan)) { + DataManager::instance().queueFlightplanUpdate(flightplan); + } +} + +void vACDM::SetGroundState(const EuroScopePlugIn::CFlightPlan flightplan, const std::string groundstate) { + // using GRP and default Euroscope ground states + // STATE ABBREVIATION GRP STATE + // - No state(departure) NSTS + // - On Freq ONFREQ Y + // - De - Ice DE-ICE Y + // - Start - Up STUP + // - Pushback PUSH + // - Taxi TAXI + // - Line Up LINEUP Y + // - Taxi In TXIN + // - No state(arrival) NOSTATE Y + // - Parked PARK + + std::string scratchBackup(flightplan.GetControllerAssignedData().GetScratchPadString()); + flightplan.GetControllerAssignedData().SetScratchPadString(groundstate.c_str()); + flightplan.GetControllerAssignedData().SetScratchPadString(scratchBackup.c_str()); +} + +void vACDM::reloadConfiguration(bool initialLoading) { + PluginConfig newConfig; + ConfigParser parser; + + if (false == parser.parse(this->m_dllPath + this->m_configFileName, newConfig) || false == newConfig.valid) { + std::string message = "vacdm.txt:" + std::to_string(parser.errorLine()) + ": " + parser.errorMessage(); + DisplayMessage(message, "Config"); + } else { + DisplayMessage(true == initialLoading ? "Loaded the config" : "Reloaded the config", "Config"); + if (this->m_pluginConfig.serverUrl != newConfig.serverUrl) + this->changeServerUrl(newConfig.serverUrl); + else + this->checkServerConfiguration(); + + this->m_pluginConfig = newConfig; + DisplayMessage(DataManager::instance().setUpdateCycleSeconds(newConfig.updateCycleSeconds)); + tagitems::Color::updatePluginConfig(newConfig); + } +} + +void vACDM::changeServerUrl(const std::string &url) { + DataManager::instance().pause(); + Server::instance().changeServerAddress(url); + this->checkServerConfiguration(); + + DataManager::instance().resume(); + DisplayMessage("Changed URL to " + url); + Logger::instance().log(Logger::LogSender::vACDM, "Changed URL to " + url, Logger::LogLevel::Info); +} + +// Euroscope Events: + +void vACDM::OnTimer(int Counter) { + if (Counter % 5 == 0) this->runEuroscopeUpdate(); +} + +void vACDM::OnFlightPlanFlightPlanDataUpdate(EuroScopePlugIn::CFlightPlan FlightPlan) { + DataManager::instance().queueFlightplanUpdate(FlightPlan); +} + +void vACDM::OnFlightPlanControllerAssignedDataUpdate(EuroScopePlugIn::CFlightPlan FlightPlan, int DataType) { + // preemptive return to only handle relevant changes + if (EuroScopePlugIn::CTR_DATA_TYPE_SPEED == DataType || EuroScopePlugIn::CTR_DATA_TYPE_MACH == DataType || + EuroScopePlugIn::CTR_DATA_TYPE_RATE == DataType || EuroScopePlugIn::CTR_DATA_TYPE_HEADING == DataType || + EuroScopePlugIn::CTR_DATA_TYPE_DIRECT_TO == DataType) { + return; + } + DataManager::instance().queueFlightplanUpdate(FlightPlan); +} + +void vACDM::OnAirportRunwayActivityChanged() { + std::list activeAirports; + + EuroScopePlugIn::CSectorElement airport; + for (airport = this->SectorFileElementSelectFirst(EuroScopePlugIn::SECTOR_ELEMENT_AIRPORT); + airport.IsValid() == true; + airport = this->SectorFileElementSelectNext(airport, EuroScopePlugIn::SECTOR_ELEMENT_AIRPORT)) { + // skip airport if it is selected as active airport for departures or arrivals + if (false == airport.IsElementActive(true, 0) && false == airport.IsElementActive(false, 0)) continue; + + // get the airport ICAO + auto airportICAO = utils::String::findIcao(utils::String::trim(airport.GetName())); + // skip airport if no ICAO has been found + if (airportICAO == "") continue; + + // check if the airport has been added already, add if it does not exist + if (std::find(activeAirports.begin(), activeAirports.end(), airportICAO) == activeAirports.end()) { + activeAirports.push_back(airportICAO); + } + } + + if (activeAirports.empty()) { + Logger::instance().log(Logger::LogSender::vACDM, + "Airport/Runway Change, no active airports: ", Logger::LogLevel::Info); + } else { + Logger::instance().log( + Logger::LogSender::vACDM, + "Airport/Runway Change, active airports: " + + std::accumulate(std::next(activeAirports.begin()), activeAirports.end(), activeAirports.front(), + [](const std::string &acc, const std::string &str) { return acc + " " + str; }), + Logger::LogLevel::Info); + } + DataManager::instance().setActiveAirports(activeAirports); +} +void vACDM::OnFunctionCall(int functionId, const char *itemString, POINT pt, RECT area) { + tagfunctions::handleTagFunction(this, functionId, itemString, pt, area); +} + +void vACDM::OnGetTagItem(EuroScopePlugIn::CFlightPlan FlightPlan, EuroScopePlugIn::CRadarTarget RadarTarget, + int ItemCode, int TagData, char sItemString[16], int *pColorCode, COLORREF *pRGB, + double *pFontSize) { + tagitems::displayTagItem(FlightPlan, RadarTarget, ItemCode, TagData, sItemString, pColorCode, pRGB, pFontSize); +} + +bool vACDM::OnCompileCommand(const char *sCommandLine) { + std::string command(sCommandLine); + +#pragma warning(push) +#pragma warning(disable : 4244) + std::transform(command.begin(), command.end(), command.begin(), ::toupper); +#pragma warning(pop) + + // only handle commands containing ".vacdm" + if (0 != command.find(".VACDM")) return false; + + // master command + if (std::string::npos != command.find("MASTER")) { + bool userIsConnected = this->GetConnectionType() != EuroScopePlugIn::CONNECTION_TYPE_NO; + bool userIsInSweatbox = this->GetConnectionType() == EuroScopePlugIn::CONNECTION_TYPE_SWEATBOX; + bool userIsObserver = std::string_view(this->ControllerMyself().GetCallsign()).ends_with("_OBS") == true || + this->ControllerMyself().GetFacility() == 0; + bool serverAllowsObsAsMaster = com::Server::instance().getServerConfig().allowMasterAsObserver; + bool serverAllowsSweatboxAsMaster = com::Server::instance().getServerConfig().allowMasterInSweatbox; + + std::string userIsNotEligibleMessage; + + if (!userIsConnected) { + userIsNotEligibleMessage = "You are not logged in to the VATSIM network"; + } else if (userIsObserver && !serverAllowsObsAsMaster) { + userIsNotEligibleMessage = "You are logged in as Observer and Server does not allow Observers to be Master"; + } else if (userIsInSweatbox && !serverAllowsSweatboxAsMaster) { + userIsNotEligibleMessage = + "You are logged in on a Sweatbox Server and Server does not allow Sweatbox connections"; + } else { + DisplayMessage("Executing vACDM as the MASTER"); + Logger::instance().log(Logger::LogSender::vACDM, "Switched to MASTER", Logger::LogLevel::Info); + com::Server::instance().setMaster(true); + + return true; + } + + DisplayMessage("Cannot upgrade to Master"); + DisplayMessage(userIsNotEligibleMessage); + return true; + } else if (std::string::npos != command.find("SLAVE")) { + DisplayMessage("Executing vACDM as the SLAVE"); + Logger::instance().log(Logger::LogSender::vACDM, "Switched to SLAVE", Logger::LogLevel::Info); + com::Server::instance().setMaster(false); + return true; + } else if (std::string::npos != command.find("RELOAD")) { + this->reloadConfiguration(); + return true; + } else if (std::string::npos != command.find("LOG")) { + if (std::string::npos != command.find("LOGLEVEL")) { + DisplayMessage(Logger::instance().handleLogLevelCommand(command)); + } else { + DisplayMessage(Logger::instance().handleLogCommand(command)); + } + return true; + } else if (std::string::npos != command.find("UPDATERATE")) { + const auto elements = vacdm::utils::String::splitString(command, " "); + if (elements.size() != 3) { + DisplayMessage("Usage: .vacdm UPDATERATE value"); + return true; + } + if (false == isNumber(elements[2]) || + std::stoi(elements[2]) < minUpdateCycleSeconds && std::stoi(elements[2]) > maxUpdateCycleSeconds) { + DisplayMessage("Usage: .vacdm UPDATERATE value"); + DisplayMessage("Value must be number between " + std::to_string(minUpdateCycleSeconds) + " and " + + std::to_string(maxUpdateCycleSeconds)); + return true; + } + + DisplayMessage(DataManager::instance().setUpdateCycleSeconds(std::stoi(elements[2]))); + + return true; + } + return false; +} + +} // namespace vacdm \ No newline at end of file diff --git a/src/vACDM.h b/src/vACDM.h new file mode 100644 index 0000000..b83f92e --- /dev/null +++ b/src/vACDM.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#pragma warning(push, 0) +#include "EuroScopePlugIn.h" +#pragma warning(pop) + +#include "config/ConfigParser.h" + +namespace vacdm { + +class vACDM : public EuroScopePlugIn::CPlugIn { + public: + vACDM(); + ~vACDM(); + + void DisplayMessage(const std::string &message, const std::string &sender = "vACDM"); + void SetGroundState(const EuroScopePlugIn::CFlightPlan flightplan, const std::string groundstate); + + // Euroscope events + void OnAirportRunwayActivityChanged() override; + void OnTimer(int Counter) override; + void OnFlightPlanFlightPlanDataUpdate(EuroScopePlugIn::CFlightPlan FlightPlan) override; + void OnFlightPlanControllerAssignedDataUpdate(EuroScopePlugIn::CFlightPlan FlightPlan, int DataType) override; + void OnFunctionCall(int functionId, const char *itemString, POINT pt, RECT area) override; + void OnGetTagItem(EuroScopePlugIn::CFlightPlan FlightPlan, EuroScopePlugIn::CRadarTarget RadarTarget, int ItemCode, + int TagData, char sItemString[16], int *pColorCode, COLORREF *pRGB, double *pFontSize) override; + bool OnCompileCommand(const char *sCommandLine) override; + + private: + std::string m_dllPath; + std::string m_configFileName = "\\vacdm.txt"; + PluginConfig m_pluginConfig; + void changeServerUrl(const std::string &url); + + void runEuroscopeUpdate(); + void checkServerConfiguration(); + void reloadConfiguration(bool initialLoading = false); +}; + +} // namespace vacdm diff --git a/types/SystemConfig.h b/types/SystemConfig.h deleted file mode 100644 index a364668..0000000 --- a/types/SystemConfig.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#pragma warning(push, 0) -#include "EuroScopePlugIn.h" -#pragma warning(pop) - -#include - -namespace vacdm { - struct SystemConfig { - bool valid = true; - std::string serverUrl = "https://vacdm.vatsim-germany.org"; - COLORREF lightgreen = RGB(127, 252, 73); - COLORREF lightblue = RGB(53, 218, 235); - COLORREF green = RGB(0, 181, 27); - COLORREF blue = RGB(0, 0, 255); - COLORREF lightyellow = RGB(255, 255, 191); - COLORREF yellow = RGB(255, 255, 0); - COLORREF orange = RGB(255, 153, 0); - COLORREF red = RGB(255, 0, 0); - COLORREF grey = RGB(153, 153, 153); - COLORREF white = RGB(255, 255, 255); - COLORREF debug = RGB(255, 0, 255); - }; -} diff --git a/ui/color.cpp b/ui/color.cpp deleted file mode 100644 index e4e11fb..0000000 --- a/ui/color.cpp +++ /dev/null @@ -1,311 +0,0 @@ -#include "color.h" - -using namespace vacdm; - -Color::Color() : - m_pluginConfig() {} - -// Times: - -COLORREF Color::colorizeEobtAndTobt(const types::Flight_t& flight) const -{ - const auto now = std::chrono::utc_clock::now(); - const auto timeSinceTobt = std::chrono::duration_cast(now - flight.tobt).count(); - const auto timeSinceTsat = std::chrono::duration_cast(now - flight.tsat).count(); - const auto diffTsatTobt = std::chrono::duration_cast(flight.tsat - flight.tobt).count(); - - if (flight.tsat == types::defaultTime) - { - return this->m_pluginConfig.grey; - } - // ASAT exists - if (flight.asat.time_since_epoch().count() > 0) - { - return this->m_pluginConfig.grey; - } - // TOBT in past && TSAT expired, i.e. 5min past TSAT || TOBT >= +1h || TSAT does not exist && TOBT in past - // -> TOBT in past && (TSAT expired || TSAT does not exist) || TOBT >= now + 1h - if (timeSinceTobt > 0 && (timeSinceTsat >= 5 * 60 || flight.tsat == types::defaultTime) || flight.tobt >= now + std::chrono::hours(1)) //last statement could cause problems - { - return this->m_pluginConfig.orange; - } - // Diff TOBT TSAT >= 5min && unconfirmed - if (diffTsatTobt >= 5 * 60 && (flight.tobt_state == "GUESS" || flight.tobt_state == "FLIGHTPLAN")) - { - return this->m_pluginConfig.lightyellow; - } - // Diff TOBT TSAT >= 5min && confirmed - if (diffTsatTobt >= 5 * 60 && flight.tobt_state == "CONFIRMED") - { - return this->m_pluginConfig.yellow; - } - // Diff TOBT TSAT < 5min - if (diffTsatTobt < 5 * 60 && flight.tobt_state == "CONFIRMED") - { - return this->m_pluginConfig.green; - } - // tobt is not confirmed - if (flight.tobt_state != "CONFIRMED") - { - return this->m_pluginConfig.lightgreen; - } - return this->m_pluginConfig.debug; -} - -COLORREF Color::colorizeTsat(const types::Flight_t& flight) const { - if (flight.asat != types::defaultTime || flight.tsat == types::defaultTime) - { - return this->m_pluginConfig.grey; - } - const auto timeSinceTsat = std::chrono::duration_cast(std::chrono::utc_clock::now() - flight.tsat).count(); - if (timeSinceTsat <= 5 * 60 && timeSinceTsat >= -5 * 60) - { - if (flight.ctot.time_since_epoch().count() > 0) - { - // CTOT exists - return this->m_pluginConfig.blue; - } - return this->m_pluginConfig.green; - } - // TSAT earlier than 5+ min - if (timeSinceTsat < -5 * 60) - { - if (flight.ctot.time_since_epoch().count() > 0) - { - // CTOT exists - return this->m_pluginConfig.lightblue; - } - return this->m_pluginConfig.lightgreen; - } - // TSAT passed by 5+ min - if (timeSinceTsat > 5 * 60) - { - if (flight.ctot.time_since_epoch().count() > 0) - { - // CTOT exists - return this->m_pluginConfig.red; - } - return this->m_pluginConfig.orange; - } - return this->m_pluginConfig.debug; -} - -COLORREF Color::colorizeTtot(const types::Flight_t& flight) const { - if (flight.ttot == types::defaultTime) - { - return this->m_pluginConfig.grey; - } - - auto now = std::chrono::utc_clock::now(); - - // Round up to the next 10, 20, 30, 40, 50, or 00 minute interval - auto timeSinceEpoch = flight.ttot.time_since_epoch(); - auto minutesSinceEpoch = std::chrono::duration_cast(timeSinceEpoch); - std::chrono::time_point rounded; - - // Compute the number of minutes remaining to the next highest ten - auto remainingMinutes = 10 - minutesSinceEpoch.count() % 10; - - // If the time point is already at a multiple of ten minutes, no rounding is needed - if (remainingMinutes == 10) { - rounded = std::chrono::time_point_cast(flight.ttot); - } - else { - // Add the remaining minutes to the time point - auto roundedUpMinutes = minutesSinceEpoch + std::chrono::minutes(remainingMinutes); - - // Convert back to a time_point object and return - rounded = std::chrono::time_point_cast(std::chrono::utc_clock::time_point(roundedUpMinutes)); - rounded += std::chrono::seconds(30); - } - - // Check if the current time has passed the ttot time point - if (flight.atot.time_since_epoch().count() > 0) - { - // ATOT exists - return this->m_pluginConfig.grey; - } - if (now < rounded) - { - // time before TTOT and during TTOT block - return this->m_pluginConfig.green; - } - else if (now >= rounded) - { - // time past TTOT / TTOT block - return this->m_pluginConfig.orange; - } - return this->m_pluginConfig.debug; -} - -COLORREF Color::colorizeAort(const types::Flight_t& flight) const { - if (flight.aort == types::defaultTime) - { - return this->m_pluginConfig.grey; - } - if (flight.aobt.time_since_epoch().count() > 0) - { - return this->m_pluginConfig.grey; - } - const auto timeSinceAort = std::chrono::duration_cast(std::chrono::utc_clock::now() - flight.aort).count(); - - if (timeSinceAort <= 5 * 60 && timeSinceAort >= 0) - { - return this->m_pluginConfig.green; - } - if (timeSinceAort > 5 * 60 && timeSinceAort <= 10 * 60) - { - return this->m_pluginConfig.yellow; - } - if (timeSinceAort > 10 * 60 && timeSinceAort <= 15 * 60) - { - return this->m_pluginConfig.orange; - } - if (timeSinceAort > 15 * 60) - { - return this->m_pluginConfig.red; - } - - return this->m_pluginConfig.debug; -} - -COLORREF Color::colorizeAsrt(const types::Flight_t& flight) const { - - if (flight.asat.time_since_epoch().count() > 0) - { - return this->m_pluginConfig.grey; - } - const auto timeSinceAsrt = std::chrono::duration_cast(std::chrono::utc_clock::now() - flight.asrt).count(); - if (timeSinceAsrt <= 5 * 60 && timeSinceAsrt >= 0) - { - return this->m_pluginConfig.green; - } - if (timeSinceAsrt > 5 * 60 && timeSinceAsrt <= 10 * 60) - { - return this->m_pluginConfig.yellow; - } - if (timeSinceAsrt > 10 * 60 && timeSinceAsrt <= 15 * 60) - { - return this->m_pluginConfig.orange; - } - if (timeSinceAsrt > 15 * 60) - { - return this->m_pluginConfig.red; - } - - return this->m_pluginConfig.debug; -} - -COLORREF Color::colorizeAsat(const types::Flight_t& flight) const { - if (flight.asat == types::defaultTime) - { - return this->m_pluginConfig.grey; - } - - if (flight.aobt.time_since_epoch().count() > 0) - { - return this->m_pluginConfig.grey; - } - - const auto timeSinceAsat = std::chrono::duration_cast(std::chrono::utc_clock::now() - flight.asat).count(); - const auto timeSinceTsat = std::chrono::duration_cast(std::chrono::utc_clock::now() - flight.tsat).count(); - if (flight.taxizoneIsTaxiout == false) - { - - if (/* Datalink clearance == true &&*/ timeSinceTsat >= -5 * 60 && timeSinceTsat <= 5 * 60) - { - return this->m_pluginConfig.green; - } - if (timeSinceAsat < 5 * 60) - { - return this->m_pluginConfig.green; - } - } - if (flight.taxizoneIsTaxiout == true) - { - if (timeSinceTsat >= -5 * 60 && timeSinceTsat <= 10 * 60 /* && Datalink clearance == true*/) - { - return this->m_pluginConfig.green; - } - if (timeSinceAsat < 10 * 60) - { - return this->m_pluginConfig.green; - } - } - return this->m_pluginConfig.orange; -} - -// Timers: - -COLORREF Color::colorizeAsatTimer(const types::Flight_t& flight) const { - // aort set - if (flight.aort.time_since_epoch().count() > 0) - { - return this->m_pluginConfig.grey; - } - const auto timeSinceAobt = std::chrono::duration_cast(std::chrono::utc_clock::now() - flight.aobt).count(); - if (timeSinceAobt >= 0) - { - // hide Timer - } - const auto timeSinceAsat = std::chrono::duration_cast(std::chrono::utc_clock::now() - flight.asat).count(); - const auto timeSinceTsat = std::chrono::duration_cast(std::chrono::utc_clock::now() - flight.tsat).count(); - // Pushback required - if (flight.taxizoneIsTaxiout != false) - { - /* - if (hasdatalinkclearance == true && timesincetsat >=5*60 && timesincetsat <=5*60) - { - return this->m_pluginConfig.green - } */ - if (timeSinceAsat < 5 * 60) - { - return this->m_pluginConfig.green; - } - } - if (flight.taxizoneIsTaxiout == true) - { - if (timeSinceTsat >= -5 * 60 && timeSinceTsat <= 10 * 60) - { - return this->m_pluginConfig.green; - } - if (timeSinceAsat <= 10 * 60) - { - return this->m_pluginConfig.green; - } - } - return this->m_pluginConfig.orange; -} - -COLORREF Color::colorizeCtotandCtottimer(const types::Flight_t& flight) const { - if (flight.ctot == types::defaultTime) - { - return this->m_pluginConfig.grey; - } - - const auto timetoctot = std::chrono::duration_cast(std::chrono::utc_clock::now() - flight.ctot).count(); - if (timetoctot >= 5 * 60) - { - return this->m_pluginConfig.lightgreen; - } - if (timetoctot <= 5 * 60 && timetoctot >= -10 * 60) - { - return this->m_pluginConfig.green; - } - if (timetoctot < -10 * 60) - { - return this->m_pluginConfig.orange; - } - - return this->m_pluginConfig.grey; -} - -void Color::changePluginConfig(const SystemConfig newPluginConfig) { - this->m_pluginConfig = newPluginConfig; -} - -Color& vacdm::Color::instance() -{ - static Color __instance; - return __instance; -} \ No newline at end of file diff --git a/ui/color.h b/ui/color.h deleted file mode 100644 index 5905a40..0000000 --- a/ui/color.h +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include - -namespace vacdm { - -class Color { -public: - Color(); - - // Times: - COLORREF colorizeEobtAndTobt(const types::Flight_t& flight) const; - COLORREF colorizeTsat(const types::Flight_t& flight) const; - COLORREF colorizeTtot(const types::Flight_t& flight) const; - COLORREF colorizeAort(const types::Flight_t& flight) const; - COLORREF colorizeAsrt(const types::Flight_t& flight) const; - COLORREF colorizeAsat(const types::Flight_t& flight) const; - - // Timers: - COLORREF colorizeAsatTimer(const types::Flight_t& flight) const; - COLORREF colorizeCtotandCtottimer(const types::Flight_t& flight) const; - - SystemConfig m_pluginConfig; - void changePluginConfig(const SystemConfig newPluginConfig); - - Color(const Color&) = delete; - Color(Color&&) = delete; - - Color& operator=(const Color&) = delete; - Color& operator=(Color&&) = delete; - - static Color& instance(); -}; - -} \ No newline at end of file diff --git a/vACDM.cpp b/vACDM.cpp deleted file mode 100644 index 9223e10..0000000 --- a/vACDM.cpp +++ /dev/null @@ -1,814 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include - -#include "vACDM.h" -#include "Version.h" -#include - -EXTERN_C IMAGE_DOS_HEADER __ImageBase; - -namespace vacdm { - -void vACDM::checkServerConfiguration() { - if (false == com::Server::instance().checkWepApi()) { - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Incompatible server version found!", true, true, true, true, false); - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Error message:", true, true, true, true, false); - this->DisplayUserMessage("vACDM", PLUGIN_NAME, com::Server::instance().errorMessage().c_str(), true, true, true, true, false); - } - else { - this->m_config = com::Server::instance().serverConfiguration(); - if (this->m_config.name.length() != 0) { - this->DisplayUserMessage("vACDM", PLUGIN_NAME, ("Connected to " + this->m_config.name).c_str(), true, true, true, true, false); - if (true == this->m_config.masterInSweatbox) - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Master in Sweatbox allowed", true, true, true, true, false); - if (true == this->m_config.masterAsObserver) - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Master as observer allowed", true, true, true, true, false); - } - } -} - -vACDM::vACDM() : - CPlugIn(EuroScopePlugIn::COMPATIBILITY_CODE, PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR, PLUGIN_LICENSE), - m_config(), - m_activeRunways(), - m_airportLock(), - m_airports() { - if (0 != curl_global_init(CURL_GLOBAL_ALL)) - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Unable to initialize the network stack!", true, true, true, true, false); - - /* get the dll-path */ - char path[MAX_PATH + 1] = { 0 }; - GetModuleFileNameA((HINSTANCE)&__ImageBase, path, MAX_PATH); - PathRemoveFileSpecA(path); - this->m_settingsPath = std::string(path) + "\\vacdm.txt"; - - this->reloadConfiguration(); - - RegisterTagItemFuntions(); - RegisterTagItemTypes(); - - this->checkServerConfiguration(); - - this->OnAirportRunwayActivityChanged(); - - logging::Logger::instance().log("vACDM", logging::Logger::Level::Info, "Initialized vACDM"); -} - -vACDM::~vACDM() { - curl_global_cleanup(); -} - -static __inline std::string ltrim(const std::string& str) { - size_t start = str.find_first_not_of(" \n\r\t\f\v"); - return (start == std::string::npos) ? "" : str.substr(start); -} - -static __inline std::string rtrim(const std::string& str) { - size_t end = str.find_last_not_of(" \n\r\t\f\v"); - return (end == std::string::npos) ? "" : str.substr(0, end + 1); -} - -static __inline std::string trim(const std::string& str) { - return rtrim(ltrim(str)); -} - -void vACDM::reloadConfiguration() { - SystemConfig newConfig; - FileFormat parser; - - if (false == parser.parse(this->m_settingsPath, newConfig) || false == newConfig.valid) { - std::string message = "vacdm.txt:" + std::to_string(parser.errorLine()) + ": " + parser.errorMessage(); - this->DisplayUserMessage("vACDM", PLUGIN_NAME, message.c_str(), true, true, true, true, false); - } - else { - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Reloaded the configuration", true, true, true, true, false); - if (this->m_pluginConfig.serverUrl != newConfig.serverUrl) - this->changeServerUrl(newConfig.serverUrl); - this->m_pluginConfig = newConfig; - Color::instance().changePluginConfig(newConfig); - } -} - -void vACDM::OnAirportRunwayActivityChanged() { - std::list activeAirports; - m_activeRunways.clear(); - - EuroScopePlugIn::CSectorElement rwy; - for (rwy = this->SectorFileElementSelectFirst(EuroScopePlugIn::SECTOR_ELEMENT_RUNWAY); true == rwy.IsValid(); - rwy = this->SectorFileElementSelectNext(rwy, EuroScopePlugIn::SECTOR_ELEMENT_RUNWAY)) { - - auto airport = helper::String::findIcao(trim(rwy.GetAirportName())); - - if (true == rwy.IsElementActive(true, 0)) { - if (m_activeRunways.find(airport) == m_activeRunways.end()) - m_activeRunways.insert({ airport, {} }); - m_activeRunways[airport].push_back(rwy.GetRunwayName(0)); - - if (activeAirports.end() == std::find(activeAirports.begin(), activeAirports.end(), airport)) - activeAirports.push_back(airport); - - logging::Logger::instance().log("vACDM", logging::Logger::Level::Info, "Active runway: " + airport + ", " + rwy.GetRunwayName(0)); - } - if (true == rwy.IsElementActive(true, 1)) { - if (m_activeRunways.find(airport) == m_activeRunways.end()) - m_activeRunways.insert({ airport, {} }); - m_activeRunways[airport].push_back(rwy.GetRunwayName(1)); - - if (activeAirports.end() == std::find(activeAirports.begin(), activeAirports.end(), airport)) - activeAirports.push_back(airport); - - logging::Logger::instance().log("vACDM", logging::Logger::Level::Info, "Active runway: " + airport + ", " + rwy.GetRunwayName(1)); - } - } - - std::lock_guard guard(this->m_airportLock); - for (auto it = this->m_airports.begin(); this->m_airports.end() != it;) { - if (activeAirports.end() == std::find(activeAirports.begin(), activeAirports.end(), (*it)->airport())) { - it = this->m_airports.erase(it); - } - else { - activeAirports.remove_if([it](const std::string& airport) { return (*it)->airport() == airport; }); - ++it; - } - } - - for (const auto& icao : std::as_const(activeAirports)) - this->m_airports.push_back(std::unique_ptr(new com::Airport(icao))); -} - -EuroScopePlugIn::CRadarScreen* vACDM::OnRadarScreenCreated(const char* displayName, bool needsRadarContent, bool geoReferenced, - bool canBeSaved, bool canBeCreated) { - std::ignore = needsRadarContent; - std::ignore = geoReferenced; - std::ignore = canBeSaved; - std::ignore = canBeCreated; - std::ignore = displayName; - - this->OnAirportRunwayActivityChanged(); - - return nullptr; -} - -void vACDM::OnFlightPlanControllerAssignedDataUpdate(EuroScopePlugIn::CFlightPlan flightplan, const int dataType) { - /* handle only relevant changes */ - if (EuroScopePlugIn::CTR_DATA_TYPE_TEMPORARY_ALTITUDE != dataType && EuroScopePlugIn::CTR_DATA_TYPE_SQUAWK != dataType) - return; - - this->updateFlight(flightplan); -} - -void vACDM::OnRadarTargetPositionUpdate(EuroScopePlugIn::CRadarTarget RadarTarget) { - - std::string callsign = RadarTarget.GetCallsign(); - if (callsign != RadarTarget.GetCorrelatedFlightPlan().GetCallsign() || callsign == "" || callsign.length() == 0) { - return; - } - - std::string_view origin(RadarTarget.GetCorrelatedFlightPlan().GetFlightPlanData().GetOrigin()); - - { - std::lock_guard guard(this->m_airportLock); - for (auto& airport : this->m_airports) { - if (airport->airport() == origin) { - if (false == airport->flightExists(callsign)) { - break; - } - - const auto& flightData = airport->flight(callsign); - - // check for AOBT and ATOT - if (flightData.asat.time_since_epoch().count() > 0) { - if (flightData.aobt.time_since_epoch().count() <= 0) { - float distanceMeters = 0.0f; - - GeographicLib::Geodesic::WGS84().Inverse(static_cast(RadarTarget.GetPosition().GetPosition().m_Latitude), static_cast(RadarTarget.GetPosition().GetPosition().m_Longitude), - static_cast(flightData.latitude), static_cast(flightData.longitude), distanceMeters); - - if (distanceMeters >= 5.0f) { - airport->updateAobt(callsign, std::chrono::utc_clock::now()); - } - } - else if (flightData.atot.time_since_epoch().count() <= 0) { - if (RadarTarget.GetGS() > 50) { - airport->updateAtot(callsign, std::chrono::utc_clock::now()); - } - } - } - break; - } - } - } - - this->updateFlight(RadarTarget.GetCorrelatedFlightPlan()); -} - -void vACDM::OnFlightPlanDisconnect(EuroScopePlugIn::CFlightPlan FlightPlan) { - if (this->GetConnectionType() != EuroScopePlugIn::CONNECTION_TYPE_DIRECT && this->GetConnectionType() != EuroScopePlugIn::CONNECTION_TYPE_VIA_PROXY) - return; - - std::lock_guard guard(this->m_airportLock); - for (auto& airport : this->m_airports) { - if (airport->airport() == FlightPlan.GetFlightPlanData().GetOrigin()) { - airport->flightDisconnected(FlightPlan.GetCorrelatedRadarTarget().GetCallsign()); - break; - } - } -} - -void vACDM::OnTimer(const int Counter) { - if (Counter % 5 == 0) - this->GetAircraftDetails(); -} - -void vACDM::OnGetTagItem(EuroScopePlugIn::CFlightPlan FlightPlan, EuroScopePlugIn::CRadarTarget RadarTarget, int ItemCode, int TagData, - char sItemString[16], int* pColorCode, COLORREF* pRGB, double* pFontSize) { - std::ignore = RadarTarget; - std::ignore = TagData; - std::ignore = pRGB; - std::ignore = pFontSize; - - *pColorCode = EuroScopePlugIn::TAG_COLOR_RGB_DEFINED; - if (nullptr == FlightPlan.GetFlightPlanData().GetPlanType() || 0 == std::strlen(FlightPlan.GetFlightPlanData().GetPlanType())) - return; - if (std::string_view("I") != FlightPlan.GetFlightPlanData().GetPlanType()) { - *pRGB = (190 << 16) | (190 << 8) | 190; - std::strcpy(sItemString, "----"); - return; - } - - std::string_view origin(FlightPlan.GetFlightPlanData().GetOrigin()); - std::lock_guard guard(this->m_airportLock); - for (auto& airport : this->m_airports) { - if (airport->airport() == origin) { - if (true == airport->flightExists(FlightPlan.GetCallsign())) { - const auto& data = airport->flight(FlightPlan.GetCallsign()); - std::stringstream stream; - - switch (static_cast(ItemCode)) { - case itemType::EOBT: - if (data.eobt.time_since_epoch().count() > 0) { - stream << std::format("{0:%H%M}", data.eobt); - *pRGB = Color::instance().colorizeEobtAndTobt(data); - } - break; - case itemType::TOBT: - if (data.tobt.time_since_epoch().count() > 0) { - stream << std::format("{0:%H%M}", data.tobt); - *pRGB = Color::instance().colorizeEobtAndTobt(data); - } - break; - case itemType::TSAT: - if (data.tsat.time_since_epoch().count() > 0) { - if (data.asat == types::defaultTime && data.asrt.time_since_epoch().count() > 0) { - stream << std::format("{0:%H%M}", data.tsat) << "R"; // "R" = aircraft ready symbol - } - else { - stream << std::format("{0:%H%M}", data.tsat); - } - *pRGB = Color::instance().colorizeTsat(data); - } - break; - case itemType::EXOT: - *pColorCode = EuroScopePlugIn::TAG_COLOR_DEFAULT; - if (data.exot.time_since_epoch().count() > 0) - stream << std::format("{0:%M}", data.exot); - break; - case itemType::TTOT: - if (data.ttot.time_since_epoch().count() > 0) { - stream << std::format("{0:%H%M}", data.ttot); - *pRGB = Color::instance().colorizeTtot(data); - } - break; - case itemType::ASAT: - if (data.asat.time_since_epoch().count() > 0) { - stream << std::format("{0:%H%M}", data.asat); - *pRGB = Color::instance().colorizeAsat(data); - } - break; - case itemType::AOBT: - if (data.aobt.time_since_epoch().count() > 0) { - stream << std::format("{0:%H%M}", data.aobt); - *pRGB = this->m_pluginConfig.grey; - } - break; - case itemType::ATOT: - if (data.atot.time_since_epoch().count() > 0) { - stream << std::format("{0:%H%M}", data.atot); - *pRGB = this->m_pluginConfig.grey; - } - break; - case itemType::ASRT: - if (data.asrt.time_since_epoch().count() > 0) { - stream << std::format("{0:%H%M}", data.asrt); - *pRGB = Color::instance().colorizeAsrt(data); - } - break; - case itemType::AORT: - if (data.aort.time_since_epoch().count() > 0) { - stream << std::format("{0:%H%M}", data.aort); - *pRGB = Color::instance().colorizeAort(data); - } - break; - case itemType::CTOT: - if (data.ctot.time_since_epoch().count() > 0) { - stream << std::format("{0:%H%M}", data.ctot); - *pRGB = Color::instance().colorizeCtotandCtottimer(data); - } - break; - case itemType::EVENT_BOOKING: - if (data.hasBooking == true) { - stream << "B"; - *pRGB = this->m_pluginConfig.green; - } - else { - stream << ""; - *pRGB = this->m_pluginConfig.grey; - } - break; - case itemType::ECFMP_MEASURES: - if (data.measures.empty() == false) { - const int measureMinutes = data.measures[0].value / 60; - const int measureSeconds = data.measures[0].value % 60; - - stream << std::format("{:02}:{:02}", measureMinutes, measureSeconds); - *pRGB = this->m_pluginConfig.green; - } - else { - stream << "------"; - *pRGB = this->m_pluginConfig.grey; - } - break; - default: - break; - } - - std::strcpy(sItemString, stream.str().c_str()); - } - break; - } - } -} - -void vACDM::changeServerUrl(const std::string& url) { - // pause all airports and reset internal data - this->m_airportLock.lock(); - for (auto& airport : this->m_airports) { - airport->pause(); - airport->resetData(); - } - this->m_airportLock.unlock(); - - com::Server::instance().changeServerAddress(url); - this->DisplayUserMessage("vACDM", PLUGIN_NAME, ("Changed vACDM URL: " + url).c_str(), true, true, true, true, false); - logging::Logger::instance().log("vACDM", logging::Logger::Level::Info, "Switched to URL: " + url); - - // validate the server - this->checkServerConfiguration(); - - // reactivate all airports - this->m_airportLock.lock(); - for (auto& airport : this->m_airports) - airport->resume(); - this->m_airportLock.unlock(); -} - -bool vACDM::OnCompileCommand(const char* sCommandLine) { - std::string message(sCommandLine); - -#pragma warning(disable: 4244) - std::transform(message.begin(), message.end(), message.begin(), ::toupper); -#pragma warning(default: 4244) - - if (0 != message.find(".VACDM")) - return false; - - if (std::string::npos != message.find("MASTER")) { - bool userConnected = this->GetConnectionType() != EuroScopePlugIn::CONNECTION_TYPE_NO; - bool userIsInSweatbox = this->GetConnectionType() != EuroScopePlugIn::CONNECTION_TYPE_DIRECT; - bool userIsObs = std::string_view(this->ControllerMyself().GetCallsign()).ends_with("_OBS") == true; - bool serverAllowsObsAsMaster = this->m_config.masterAsObserver; - bool serverAllowsSweatboxAsMaster = this->m_config.masterInSweatbox; - - std::string userIsNotEligibleMessage; - - if (!userConnected) { - userIsNotEligibleMessage = "You are not logged in to the VATSIM network"; - } - else if (userIsObs && !serverAllowsObsAsMaster) { - userIsNotEligibleMessage = "You are logged in as Observer and Server does not allow Observers to be Master"; - } - else if (userIsInSweatbox && !serverAllowsSweatboxAsMaster) { - userIsNotEligibleMessage = "You are logged in on a Sweatbox Server and Server does not allow Sweatbox connections"; - } - else { - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Executing vACDM as the MASTER", true, true, true, true, false); - logging::Logger::instance().log("vACDM", logging::Logger::Level::Info, "Switched to MASTER"); - com::Server::instance().setMaster(true); - - return true; - } - - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Cannot upgrade to Master", true, true, true, true, false); - this->DisplayUserMessage("vACDM", PLUGIN_NAME, userIsNotEligibleMessage.c_str(), true, true, true, true, false); - return true; - } - else if (std::string::npos != message.find("SLAVE")) { - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Executing vACDM as the SLAVE", true, true, true, true, false); - logging::Logger::instance().log("vACDM", logging::Logger::Level::Info, "Switched to SLAVE"); - com::Server::instance().setMaster(false); - return true; - } - else if (std::string::npos != message.find("RELOAD")) { - this->reloadConfiguration(); - return true; - } - else if (std::string::npos != message.find("URL")) { - const auto elements = helper::String::splitString(message, " "); - if (3 == elements.size()) { - this->changeServerUrl(elements[2]); - } - else { - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Unable to change vACDM URL", true, true, true, true, false); - } - return true; - } - else if (std::string::npos != message.find("LOGLEVEL")) { - const auto elements = helper::String::splitString(message, " "); - if (3 == elements.size()) { - if (elements[2] == "DEBUG") { - logging::Logger::instance().setMinimumLevel(logging::Logger::Level::Debug); - logging::Logger::instance().log("vACDM", logging::Logger::Level::System, "Switched level: DEBUG"); - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Switched level: DEBUG", true, true, true, true, false); - return true; - } - else if (elements[2] == "INFO") { - logging::Logger::instance().setMinimumLevel(logging::Logger::Level::Info); - logging::Logger::instance().log("vACDM", logging::Logger::Level::System, "Switched level: INFO"); - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Switched level: INFO", true, true, true, true, false); - return true; - } - else if (elements[2] == "WARNING") { - logging::Logger::instance().setMinimumLevel(logging::Logger::Level::Warning); - logging::Logger::instance().log("vACDM", logging::Logger::Level::System, "Switched level: WARNING"); - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Switched level : WARNING", true, true, true, true, false); - return true; - } - else if (elements[2] == "ERROR") { - logging::Logger::instance().setMinimumLevel(logging::Logger::Level::Error); - logging::Logger::instance().log("vACDM", logging::Logger::Level::System, "Switched level: ERROR"); - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Switched level: ERROR", true, true, true, true, false); - return true; - } - else if (elements[2] == "CRITICAL") { - logging::Logger::instance().setMinimumLevel(logging::Logger::Level::Critical); - logging::Logger::instance().log("vACDM", logging::Logger::Level::System, "Switched level: CRITICAL"); - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Switched level: CRITICAL", true, true, true, true, false); - return true; - } - else if (elements[2] == "OFF") { - logging::Logger::instance().setMinimumLevel(logging::Logger::Level::Disabled); - logging::Logger::instance().log("vACDM", logging::Logger::Level::System, "Switched level: DISABLED"); - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Switched level: DISABLED", true, true, true, true, false); - return true; - } - } - } - - logging::Logger::instance().log("vACDM", logging::Logger::Level::Warning, "Unknown CMDLINE: " + std::string(sCommandLine)); - return false; -} - -void vACDM::DisplayDebugMessage(const std::string &message) { - DisplayUserMessage("vACDM", "DEBUG", message.c_str(), true, false, false, false, false); -} - -std::chrono::utc_clock::time_point vACDM::convertToTobt(const std::string& callsign, const std::string& eobt) { - const auto now = std::chrono::utc_clock::now(); - if (eobt.length() == 0 || eobt.length() > 4) { - logging::Logger::instance().log("vACDM", logging::Logger::Level::Debug, "Uninitialized EOBT of " + callsign); - return now; - } - - logging::Logger::instance().log("vACDM", logging::Logger::Level::Debug, "Converting EOBT " + eobt + " of " + callsign); - - std::stringstream stream; - stream << std::format("{0:%Y%m%d}", now); - std::size_t requiredLeadingZeros = 4 - eobt.length(); - while (requiredLeadingZeros != 0) { - requiredLeadingZeros -= 1; - stream << "0"; - } - stream << eobt; - - std::chrono::utc_clock::time_point tobt; - std::stringstream input(stream.str()); - std::chrono::from_stream(stream, "%Y%m%d%H%M", tobt); - - return tobt; -} - -void vACDM::updateFlight(const EuroScopePlugIn::CFlightPlan& fp) { - // ignore irrelevant and non-IFR flights - if (nullptr == fp.GetFlightPlanData().GetPlanType() || 0 == std::strlen(fp.GetFlightPlanData().GetPlanType())) - return; - if (std::string_view("I") != fp.GetFlightPlanData().GetPlanType()) - return; - bool foundAirport = false; - for (auto& airport : this->m_airports) { - if (airport->airport() == fp.GetFlightPlanData().GetOrigin()) { - foundAirport = true; - break; - } - } - if (!foundAirport) - return; - - types::Flight_t flight; - flight.callsign = fp.GetCallsign(); - flight.inactive = false; - flight.latitude = fp.GetFPTrackPosition().GetPosition().m_Latitude; - flight.longitude = fp.GetFPTrackPosition().GetPosition().m_Longitude; - flight.origin = fp.GetFlightPlanData().GetOrigin(); - flight.destination = fp.GetFlightPlanData().GetDestination(); - flight.rule = "I"; - flight.eobt = vACDM::convertToTobt(flight.callsign, fp.GetFlightPlanData().GetEstimatedDepartureTime()); - flight.tobt = flight.eobt; - flight.runway = fp.GetFlightPlanData().GetDepartureRwy(); - flight.sid = fp.GetFlightPlanData().GetSidName(); - flight.assignedSquawk = fp.GetControllerAssignedData().GetSquawk(); - flight.currentSquawk = fp.GetFPTrackPosition().GetSquawk(); - flight.initialClimb = std::to_string(fp.GetControllerAssignedData().GetClearedAltitude()); - - if (fp.GetCorrelatedRadarTarget().GetGS() > 50) { - logging::Logger::instance().log("vACDM", logging::Logger::Level::Debug, flight.callsign + " departed. GS: " + std::to_string(fp.GetFPTrackPosition().GetReportedGS())); - flight.departed = true; - } - - std::lock_guard guard(this->m_airportLock); - for (auto& airport : this->m_airports) { - if (airport->airport() == flight.origin) { - airport->updateFromEuroscope(flight); - return; - } - } - - logging::Logger::instance().log("vACDM", logging::Logger::Level::Debug, "Inactive ADEP: " + flight.origin); -} - -void vACDM::GetAircraftDetails() { - for (auto fp = FlightPlanSelectFirst(); fp.IsValid(); fp = FlightPlanSelectNext(fp)) - this->updateFlight(fp); -} - -static __inline bool isNumber(const std::string& s) { - return !s.empty() && std::find_if(s.begin(), s.end(), [](unsigned char c) { return !std::isdigit(c); }) == s.end(); -} - -void vACDM::OnFunctionCall(int functionId, const char* itemString, POINT pt, RECT area) { - std::ignore = pt; - - auto flightplan = this->FlightPlanSelectASEL(); - std::string callsign(flightplan.GetCallsign()); - - if ("I" != std::string_view(flightplan.GetFlightPlanData().GetPlanType())) - return; - - std::shared_ptr currentAirport; - std::string_view origin(flightplan.GetFlightPlanData().GetOrigin()); - this->m_airportLock.lock(); - for (const auto& airport : std::as_const(this->m_airports)) { - if (airport->airport() == origin) { - currentAirport = airport; - break; - } - } - this->m_airportLock.unlock(); - - if (nullptr == currentAirport) - return; - - auto& data = currentAirport->flight(callsign); - - switch (static_cast(functionId)) { - case EXOT_MODIFY: - this->OpenPopupEdit(area, static_cast(itemFunction::EXOT_NEW_VALUE), itemString); - break; - case EXOT_NEW_VALUE: - if (true == isNumber(itemString)) { - const auto exot = std::chrono::utc_clock::time_point(std::chrono::minutes(std::atoi(itemString))); - if (exot != data.exot) - currentAirport->updateExot(callsign, exot); - } - break; - case TOBT_NOW: - currentAirport->updateTobt(callsign, std::chrono::utc_clock::now(), false); - break; - case TOBT_MANUAL: - this->OpenPopupEdit(area, TOBT_MANUAL_EDIT, ""); - break; - case TOBT_MANUAL_EDIT: - { - std::string clock(itemString); - if (clock.length() == 4 && isNumber(clock)) { - const auto hours = std::atoi(clock.substr(0, 2).c_str()); - const auto minutes = std::atoi(clock.substr(2, 4).c_str()); - if (hours >= 0 && hours < 24 && minutes >= 0 && minutes < 60) - currentAirport->updateTobt(callsign, this->convertToTobt(callsign, clock), true); - else - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Invalid time format. Expected: HHMM (24 hours)", true, true, true, true, false); - } else if (clock.length() != 0) { - this->DisplayUserMessage("vACDM", PLUGIN_NAME, "Invalid time format. Expected: HHMM (24 hours)", true, true, true, true, false); - } - break; - } - case ASAT_NOW: - { - currentAirport->updateAsat(callsign, std::chrono::utc_clock::now()); - // if ASRT has not been set yet -> set ASRT - if (data.asrt == types::defaultTime) - { - currentAirport->updateAsrt(callsign, std::chrono::utc_clock::now()); - } - break; - } - case ASAT_NOW_AND_STARTUP: - { - currentAirport->updateAsat(callsign, std::chrono::utc_clock::now()); - - // if ASRT has not been set yet -> set ASRT - if (data.asrt == types::defaultTime) - { - currentAirport->updateAsrt(callsign, std::chrono::utc_clock::now()); - } - - SetGroundState(flightplan, "ST-UP"); - - break; - } - case STARTUP_REQUEST: - { - currentAirport->updateAsrt(callsign, std::chrono::utc_clock::now()); - break; - } - case AOBT_NOW_AND_STATE: - { - // set ASRT if ASRT has not been set yet - if (data.asrt == types::defaultTime) { - currentAirport->updateAort(callsign, std::chrono::utc_clock::now()); - } - currentAirport->updateAobt(callsign, std::chrono::utc_clock::now()); - - // set status depending on if the aircraft is positioned at a taxi-out position - if (data.taxizoneIsTaxiout) { - SetGroundState(flightplan, "TAXI"); - } - else { - SetGroundState(flightplan, "PUSH"); - } - break; - } - case TOBT_CONFIRM: - { - currentAirport->updateTobt(callsign, data.tobt, true); - break; - } - case OFFBLOCK_REQUEST: - { - currentAirport->updateAort(callsign, std::chrono::utc_clock::now()); - break; - } - case TOBT_MENU: - { - this->OpenPopupList(area, "TOBT menu", 1); - AddPopupListElement("TOBT now", NULL, TOBT_NOW, false, 2, false, false); - AddPopupListElement("TOBT edit", NULL, TOBT_MANUAL, false, 2, false, false); - AddPopupListElement("TOBT confirm", NULL, TOBT_CONFIRM, false, 2, false, false); - break; - } - case RESET_TOBT: - { - currentAirport->resetTobt(callsign, types::defaultTime, data.tobt_state); - break; - } - case RESET_ASAT: - { - currentAirport->updateAsat(callsign, types::defaultTime); - SetGroundState(flightplan, "NSTS"); - break; - } - case RESET_ASRT: - { - currentAirport->updateAsrt(callsign, types::defaultTime); - break; - } - case RESET_TOBT_CONFIRM: - { - currentAirport->resetTobt(callsign, data.tobt, "GUESS"); - break; - } - case RESET_AORT: - { - currentAirport->updateAort(callsign, types::defaultTime); - break; - } - case RESET_AOBT_AND_STATE: - { - SetGroundState(flightplan, "NSTS"); - currentAirport->updateAobt(callsign, types::defaultTime); - break; - } - case RESET_MENU: - { - this->OpenPopupList(area, "RESET menu", 1); - AddPopupListElement("Reset TOBT", NULL, RESET_TOBT, false, 2, false, false); - AddPopupListElement("Reset ASAT", NULL, RESET_ASAT, false, 2, false, false); - AddPopupListElement("Reset ASRT", NULL, RESET_ASRT, false, 2, false, false); - AddPopupListElement("Reset confirmed TOBT", NULL, RESET_TOBT_CONFIRM, false, 2, false, false); - AddPopupListElement("Reset AORT", NULL, RESET_AORT, false, 2, false, false); - AddPopupListElement("Reset AOBT", NULL, RESET_AOBT_AND_STATE, false, 2, false, false); - AddPopupListElement("Reset Aircraft", NULL, RESET_AIRCRAFT, false, 2, false, false); - break; - } - case RESET_AIRCRAFT: - { - currentAirport->deleteFlight(callsign); - break; - } - default: - break; - } -} - -void vACDM::SetGroundState(const EuroScopePlugIn::CFlightPlan flightplan, const std::string groundstate) { - // using GRP and default Euroscope ground states - // STATE ABBREVIATION GRP STATE - // - No state(departure) NSTS - // - On Freq ONFREQ Y - // - De - Ice DE-ICE Y - // - Start - Up STUP - // - Pushback PUSH - // - Taxi TAXI - // - Line Up LINEUP Y - // - Taxi In TXIN - // - No state(arrival) NOSTATE Y - // - Parked PARK - - std::string scratchBackup(flightplan.GetControllerAssignedData().GetScratchPadString()); - flightplan.GetControllerAssignedData().SetScratchPadString(groundstate.c_str()); - flightplan.GetControllerAssignedData().SetScratchPadString(scratchBackup.c_str()); -} - -void vACDM::RegisterTagItemFuntions() { - RegisterTagItemFunction("Modify EXOT", EXOT_MODIFY); - RegisterTagItemFunction("TOBT now", TOBT_NOW); - RegisterTagItemFunction("Set TOBT", TOBT_MANUAL); - RegisterTagItemFunction("TOBT confirm", TOBT_CONFIRM); - RegisterTagItemFunction("Tobt menu", TOBT_MENU); - RegisterTagItemFunction("ASAT now", ASAT_NOW); - RegisterTagItemFunction("ASAT now and startup state", ASAT_NOW_AND_STARTUP); - RegisterTagItemFunction("Startup Request", STARTUP_REQUEST); - RegisterTagItemFunction("Request Offblock", OFFBLOCK_REQUEST); - RegisterTagItemFunction("Set AOBT and Groundstate", AOBT_NOW_AND_STATE); - // Reset Functions - RegisterTagItemFunction("Reset TOBT", RESET_TOBT); - RegisterTagItemFunction("Reset ASAT", RESET_ASAT); - RegisterTagItemFunction("Reset confirmed TOBT", RESET_TOBT_CONFIRM); - RegisterTagItemFunction("Reset Offblock Request", RESET_AORT); - RegisterTagItemFunction("Reset AOBT", RESET_AOBT_AND_STATE); - RegisterTagItemFunction("Reset Menu", RESET_MENU); - RegisterTagItemFunction("Reset aircraft", RESET_AIRCRAFT); -} - -void vACDM::RegisterTagItemTypes() { - RegisterTagItemType("EOBT", itemType::EOBT); - RegisterTagItemType("TOBT", itemType::TOBT); - RegisterTagItemType("TSAT", itemType::TSAT); - RegisterTagItemType("TTOT", itemType::TTOT); - RegisterTagItemType("EXOT", itemType::EXOT); - RegisterTagItemType("ASAT", itemType::ASAT); - RegisterTagItemType("AOBT", itemType::AOBT); - RegisterTagItemType("ATOT", itemType::ATOT); - RegisterTagItemType("ASRT", itemType::ASRT); - RegisterTagItemType("AORT", itemType::AORT); - RegisterTagItemType("CTOT", itemType::CTOT); - RegisterTagItemType("Event Booking", itemType::EVENT_BOOKING); - RegisterTagItemType("ECFMP Measures", itemType::ECFMP_MEASURES); -} - -} //namespace vacdm diff --git a/vACDM.h b/vACDM.h deleted file mode 100644 index a0bf95e..0000000 --- a/vACDM.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#pragma warning(push, 0) -#include "EuroScopePlugIn.h" -#pragma warning(pop) - -#include -#include -#include -#include - -namespace vacdm { - -enum itemType -{ - EOBT = 1, - TOBT, - TSAT, - TTOT, - EXOT, - ASAT, - AOBT, - ATOT, - ASRT, - AORT, - CTOT, - ECFMP_MEASURES, - EVENT_BOOKING, -}; - -enum itemFunction -{ - EXOT_MODIFY = 1, - EXOT_NEW_VALUE, - TOBT_NOW, - TOBT_MANUAL, - TOBT_MANUAL_EDIT, - TOBT_MENU, - ASAT_NOW, - ASAT_NOW_AND_STARTUP, - STARTUP_REQUEST, - TOBT_CONFIRM, - OFFBLOCK_REQUEST, - AOBT_NOW_AND_STATE, - RESET_TOBT, - RESET_ASAT, - RESET_ASRT, - RESET_TOBT_CONFIRM, - RESET_AORT, - RESET_AOBT_AND_STATE, - RESET_MENU, - RESET_AIRCRAFT, -}; - -class vACDM : public EuroScopePlugIn::CPlugIn { -public: - vACDM(); - ~vACDM(); - -private: - std::string m_settingsPath; - com::Server::ServerConfiguration_t m_config; - std::map> m_activeRunways; - std::mutex m_airportLock; - std::list> m_airports; - SystemConfig m_pluginConfig; - - void reloadConfiguration(); - void changeServerUrl(const std::string& url); - void updateFlight(const EuroScopePlugIn::CFlightPlan& rt); - static std::chrono::utc_clock::time_point convertToTobt(const std::string& callsign, const std::string& eobt); - - void checkServerConfiguration(); - EuroScopePlugIn::CRadarScreen* OnRadarScreenCreated(const char* displayName, bool needsRadarContent, bool geoReferenced, - bool canBeSaved, bool canBeCreated) override; - void OnAirportRunwayActivityChanged() override; - void OnFlightPlanControllerAssignedDataUpdate(EuroScopePlugIn::CFlightPlan flightplan, const int dataType) override; - void OnRadarTargetPositionUpdate(EuroScopePlugIn::CRadarTarget RadarTarget) override; - void OnFlightPlanDisconnect(EuroScopePlugIn::CFlightPlan FlightPlan) override; - void OnTimer(const int Counter) override; - void OnGetTagItem(EuroScopePlugIn::CFlightPlan FlightPlan, EuroScopePlugIn::CRadarTarget RadarTarget, int ItemCode, int TagData, - char sItemString[16], int* pColorCode, COLORREF* pRGB, double* pFontSize) override; - void OnFunctionCall(int functionId, const char* itemString, POINT pt, RECT area) override; - bool OnCompileCommand(const char* sCommandLine) override; - - void DisplayDebugMessage(const std::string &message); - void GetAircraftDetails(); - void SetGroundState(const EuroScopePlugIn::CFlightPlan flightplan, const std::string groundstate); - void RegisterTagItemFuntions(); - void RegisterTagItemTypes(); -}; - -} //namespace vacdm