Skip to content

Commit

Permalink
v1.3.0 (#43)
Browse files Browse the repository at this point in the history
major refactor to improve performance and remove technical debt
LeoKle authored Apr 16, 2024
1 parent d3cf9cf commit 3f36f8c
Showing 51 changed files with 3,452 additions and 3,180 deletions.
3 changes: 3 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 120
2 changes: 0 additions & 2 deletions .gitattributes

This file was deleted.

96 changes: 96 additions & 0 deletions .github/workflows/build-develop.yml
Original file line number Diff line number Diff line change
@@ -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 }}
89 changes: 89 additions & 0 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
@@ -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 }}
15 changes: 15 additions & 0 deletions .github/workflows/clang-format.yml
Original file line number Diff line number Diff line change
@@ -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/[email protected]
with:
clang-format-version: "13"
check-path: "src"
fallback-style: "Google"
exclude-regex: "(sqlite3)"
25 changes: 25 additions & 0 deletions .github/workflows/cmake_project_version.py
Original file line number Diff line number Diff line change
@@ -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)
57 changes: 57 additions & 0 deletions .github/workflows/determine_dev_release.py
Original file line number Diff line number Diff line change
@@ -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
34 changes: 34 additions & 0 deletions .github/workflows/version_handler.py
Original file line number Diff line number Diff line change
@@ -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")
172 changes: 85 additions & 87 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,87 +1,85 @@
# Author:
# Sven Czarnian <devel@svcz.de>
# 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)
11 changes: 8 additions & 3 deletions CMakeSettings.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
]
}
674 changes: 0 additions & 674 deletions com/Airport.cpp

This file was deleted.

77 changes: 0 additions & 77 deletions com/Airport.h

This file was deleted.

35 changes: 0 additions & 35 deletions com/PerformanceLock.h

This file was deleted.

379 changes: 0 additions & 379 deletions com/Server.cpp

This file was deleted.

77 changes: 0 additions & 77 deletions com/Server.h

This file was deleted.

137 changes: 0 additions & 137 deletions config/FileFormat.cpp

This file was deleted.

52 changes: 0 additions & 52 deletions config/FileFormat.h

This file was deleted.

120 changes: 0 additions & 120 deletions helper/String.h

This file was deleted.

63 changes: 0 additions & 63 deletions logging/Logger.cpp

This file was deleted.

33 changes: 0 additions & 33 deletions logging/Logger.h

This file was deleted.

67 changes: 0 additions & 67 deletions logging/Performance.h

This file was deleted.

27 changes: 16 additions & 11 deletions Version.h.in → src/Version.h.in
Original file line number Diff line number Diff line change
@@ -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@;
}
136 changes: 136 additions & 0 deletions src/config/ConfigParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#include "ConfigParser.h"

#include <array>
#include <fstream>
#include <limits>
#include <vector>

#include "core/DataManager.h"
#include "utils/String.h"

using namespace vacdm;

ConfigParser::ConfigParser() : m_errorLine(std::numeric_limits<std::uint32_t>::max()), m_errorMessage() {}

bool ConfigParser::errorFound() const { return std::numeric_limits<std::uint32_t>::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<std::string> colorValues = utils::String::splitString(block, ",");

if (3 != colorValues.size()) {
this->m_errorLine = line;
this->m_errorMessage = "Invalid color config";
return false;
}

std::array<std::uint8_t, 3> colors;
for (std::size_t i = 0; i < 3; ++i) colors[i] = static_cast<std::uint8_t>(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<std::string> 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;
}
25 changes: 25 additions & 0 deletions src/config/ConfigParser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <cstdint>
#include <string>

#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
26 changes: 26 additions & 0 deletions src/config/PluginConfig.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <string>

#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
13 changes: 13 additions & 0 deletions src/config/vacdm.txt
Original file line number Diff line number Diff line change
@@ -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
528 changes: 528 additions & 0 deletions src/core/DataManager.cpp

Large diffs are not rendered by default.

117 changes: 117 additions & 0 deletions src/core/DataManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#pragma once

#include <list>
#include <map>
#include <mutex>
#include <string>
#include <thread>

#pragma warning(push, 0)
#include "EuroScopePlugIn.h"
#pragma warning(pop)

#include <json/json.h>

#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<std::string, std::array<types::Pilot, 3>> m_pilots;
std::mutex m_airportLock;
std::list<std::string> m_activeAirports;

struct EuroscopeFlightplanUpdate {
std::chrono::utc_clock::time_point timeIssued;
EuroScopePlugIn::CFlightPlan data;
};

std::mutex m_euroscopeUpdatesLock;
std::list<EuroscopeFlightplanUpdate> 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<EuroscopeFlightplanUpdate> &list);
/// @brief updates the pilots with the saved EuroScope flightplan updates
/// @param pilots to update
void processEuroScopeUpdates(std::map<std::string, std::array<types::Pilot, 3U>> &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<std::string, std::array<types::Pilot, 3U>> &pilots);
/// @brief consolidates EuroScope and backend data
/// @param pilot
void consolidateData(std::array<types::Pilot, 3> &pilot);

MessageType deltaEuroscopeToBackend(const std::array<types::Pilot, 3> &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<struct AsynchronousMessage> m_asynchronousMessages;
void processAsynchronousMessages(std::map<std::string, std::array<types::Pilot, 3U>> &pilots);

public:
void setActiveAirports(const std::list<std::string> 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
496 changes: 496 additions & 0 deletions src/core/Server.cpp

Large diffs are not rendered by default.

87 changes: 87 additions & 0 deletions src/core/Server.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#pragma once

#define CURL_STATICLIB 1
#include <curl/curl.h>
#include <json/json.h>

#include <list>
#include <mutex>
#include <string>

#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<types::Pilot> getPilots(const std::list<std::string> 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
218 changes: 218 additions & 0 deletions src/core/TagFunctions.h
Original file line number Diff line number Diff line change
@@ -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<itemFunction>(functionId)) {
case EXOT_MODIFY:
plugin->OpenPopupEdit(area, static_cast<int>(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
136 changes: 136 additions & 0 deletions src/core/TagItems.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#pragma once

#include <wtypes.h>

#include <chrono>
#include <format>
#include <string>

#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<itemType>(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
305 changes: 305 additions & 0 deletions src/core/TagItemsColor.h
Original file line number Diff line number Diff line change
@@ -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::seconds>(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<std::chrono::minutes>(timeSinceEpoch);
std::chrono::time_point<std::chrono::utc_clock> 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<std::chrono::minutes>(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::minutes>(
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::seconds>(std::chrono::utc_clock::now() - pilot.asat).count();
const auto timeSinceTsat =
std::chrono::duration_cast<std::chrono::seconds>(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::seconds>(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::seconds>(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::seconds>(std::chrono::utc_clock::now() - pilot.aobt).count();
if (timeSinceAobt >= 0) {
// hide Timer
}
const auto timeSinceAsat =
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::utc_clock::now() - pilot.asat).count();
const auto timeSinceTsat =
std::chrono::duration_cast<std::chrono::seconds>(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<std::chrono::seconds>(now - pilot.tobt).count();
const auto timeSinceTsat = std::chrono::duration_cast<std::chrono::seconds>(now - pilot.tsat).count();
const auto diffTsatTobt = std::chrono::duration_cast<std::chrono::seconds>(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::seconds>(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
204 changes: 204 additions & 0 deletions src/log/Logger.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
#include "Logger.h"

#ifdef DEBUG_BUILD
#include <Windows.h>

#include <iostream>
#endif

#include <algorithm>
#include <chrono>
#include <numeric>

#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<int>(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;
}
88 changes: 88 additions & 0 deletions src/log/Logger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#pragma once

#include <list>
#include <mutex>
#include <sstream>
#include <string>
#include <vector>

#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<LogSetting> 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<LogSetting> 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<struct AsynchronousLog> 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
File renamed without changes.
File renamed without changes.
File renamed without changes.
33 changes: 15 additions & 18 deletions main.cpp → src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
#include <memory>
#pragma warning(push, 0)
#include <EuroScopePlugIn.h>
#pragma warning(pop)

#include "vACDM.h"

std::unique_ptr<EuroScopePlugIn::CPlugIn> Plugin;

void __declspec (dllexport) EuroScopePlugInInit(EuroScopePlugIn::CPlugIn **ppPlugInInstance)
{
Plugin.reset(new vacdm::vACDM());
*ppPlugInInstance = Plugin.get();
}

void __declspec (dllexport) EuroScopePlugInExit(void)
{
}
#include <memory>
#pragma warning(push, 0)
#include <EuroScopePlugIn.h>
#pragma warning(pop)

#include "vACDM.h"

std::unique_ptr<EuroScopePlugIn::CPlugIn> Plugin;

void __declspec(dllexport) EuroScopePlugInInit(EuroScopePlugIn::CPlugIn **ppPlugInInstance) {
Plugin.reset(new vacdm::vACDM());
*ppPlugInInstance = Plugin.get();
}

void __declspec(dllexport) EuroScopePlugInExit(void) {}
36 changes: 36 additions & 0 deletions src/types/Ecfmp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

#include <array>
#include <chrono>
#include <string>

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<std::string> mandatoryRoute;
} EcfmpMeasure;

typedef struct EcfmpFilter_t {
std::string type;
std::vector<std::string> value;
std::vector<std::int64_t> flightlevels;
std::vector<std::string> 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<int> notified_fir_regions;
std::vector<EcfmpMeasure> measures;
std::vector<EcfmpFilter> filters;
} EcfmpFlowMeasure;
} // namespace vacdm::types
116 changes: 54 additions & 62 deletions types/Flight.h → src/types/Pilot.h
Original file line number Diff line number Diff line change
@@ -1,62 +1,54 @@
#pragma once

#include <chrono>
#include <string>

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<Measure> measures;
} Flight_t;

}
}
#pragma once

#include <chrono>
#include <string>

#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<EcfmpMeasure> measures;

// event booking data

bool hasBooking = false;
} Pilot;
} // namespace vacdm::types
113 changes: 113 additions & 0 deletions src/utils/Date.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#pragma once

#include <chrono>
#include <string>

#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 &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;
}

/// @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
9 changes: 9 additions & 0 deletions src/utils/Number.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#include <string>

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
113 changes: 113 additions & 0 deletions src/utils/String.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* @brief Defines and implements functions to handle strings
* @file helper/String.h
* @author Sven Czarnian <devel@svcz.de>
* @copyright Copyright 2020-2021 Sven Czarnian
* @license This project is published under the GNU General Public License v3
* (GPLv3)
*/

#pragma once

#include <algorithm>
#include <string>
#include <vector>

namespace vacdm::utils {
/**
* @brief Implements and defines convenience functions for the string handling
* @ingroup helper
*/
class String {
private:
template <typename Separator>
static auto splitAux(const std::string &value, Separator &&separator) -> std::vector<std::string> {
std::vector<std::string> 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<std::string> {
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
Loading

0 comments on commit 3f36f8c

Please sign in to comment.