mirror of
https://github.com/eclipse-openvehicle-api/openvehicle-api.git
synced 2026-02-05 15:18:45 +00:00
375
global/ascformat/ascreader.cpp
Normal file
375
global/ascformat/ascreader.cpp
Normal file
@@ -0,0 +1,375 @@
|
||||
#include "ascreader.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <processthreadsapi.h>
|
||||
#elif defined __unix__
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
namespace asc
|
||||
{
|
||||
CAscReader::CAscReader() : m_itCurrent(m_lstMessages.begin())
|
||||
{}
|
||||
|
||||
CAscReader::~CAscReader()
|
||||
{
|
||||
StopPlayback();
|
||||
}
|
||||
|
||||
bool CAscReader::Read(const std::filesystem::path& rpathFile)
|
||||
{
|
||||
// Clear current messages
|
||||
m_lstMessages.clear();
|
||||
JumpBegin();
|
||||
|
||||
// Open and read the file
|
||||
std::ifstream fstream(rpathFile);
|
||||
if (!fstream.is_open()) return false;
|
||||
|
||||
// Read the file line by line
|
||||
std::string ssLine;
|
||||
enum class EState {header, body, footer} eState = EState::header;
|
||||
while (std::getline(fstream, ssLine))
|
||||
{
|
||||
switch (eState)
|
||||
{
|
||||
case EState::header:
|
||||
if (ssLine.compare(0, 18, "Begin Triggerblock") == 0)
|
||||
eState = EState::body;
|
||||
break;
|
||||
case EState::body:
|
||||
if (ssLine.compare(0, 16, "End TriggerBlock") == 0)
|
||||
eState = EState::footer;
|
||||
else
|
||||
ProcessSample(ssLine);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
fstream.close();
|
||||
|
||||
// Set the current iterator
|
||||
JumpBegin();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<SCanMessage, bool> CAscReader::Get() const
|
||||
{
|
||||
if (m_itCurrent == m_lstMessages.end())
|
||||
return std::make_pair(SCanMessage(), false);
|
||||
else
|
||||
return std::make_pair(*m_itCurrent, true);
|
||||
}
|
||||
|
||||
uint32_t CAscReader::GetLoopCount() const
|
||||
{
|
||||
return m_uiLoopCount;
|
||||
}
|
||||
|
||||
void CAscReader::JumpBegin()
|
||||
{
|
||||
m_itCurrent = m_lstMessages.begin();
|
||||
}
|
||||
|
||||
void CAscReader::JumpEnd()
|
||||
{
|
||||
m_itCurrent = m_lstMessages.end();
|
||||
}
|
||||
|
||||
CAscReader& CAscReader::operator++()
|
||||
{
|
||||
if (m_itCurrent != m_lstMessages.end())
|
||||
++m_itCurrent;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CAscReader& CAscReader::operator++(int)
|
||||
{
|
||||
if (m_itCurrent != m_lstMessages.end())
|
||||
++m_itCurrent;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CAscReader& CAscReader::operator--()
|
||||
{
|
||||
if (m_itCurrent != m_lstMessages.begin())
|
||||
--m_itCurrent;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CAscReader& CAscReader::operator--(int)
|
||||
{
|
||||
if (m_itCurrent != m_lstMessages.begin())
|
||||
--m_itCurrent;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CAscReader::IsBOF() const
|
||||
{
|
||||
return m_itCurrent == m_lstMessages.begin();
|
||||
}
|
||||
|
||||
bool CAscReader::IsEOF() const
|
||||
{
|
||||
return m_itCurrent == m_lstMessages.end();
|
||||
}
|
||||
|
||||
void CAscReader::StartPlayback(std::function<void(const SCanMessage&)> fnCallback, bool bRepeat /*= true*/)
|
||||
{
|
||||
StopPlayback();
|
||||
|
||||
if (!fnCallback) return;
|
||||
if (m_lstMessages.empty()) return;
|
||||
|
||||
m_bPlaybackThread = false;
|
||||
m_bPlayback = true;
|
||||
m_threadPlayback = std::thread(&CAscReader::PlaybackThreadFunc, this, fnCallback, std::ref(m_uiLoopCount), bRepeat);
|
||||
while (!m_bPlaybackThread) std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
void CAscReader::StopPlayback()
|
||||
{
|
||||
m_bPlayback = false;
|
||||
if (m_threadPlayback.joinable()) m_threadPlayback.join();
|
||||
}
|
||||
|
||||
void CAscReader::ResetPlayback()
|
||||
{
|
||||
StopPlayback();
|
||||
JumpBegin();
|
||||
}
|
||||
|
||||
bool CAscReader::PlaybackRunning() const
|
||||
{
|
||||
return m_bPlayback;
|
||||
}
|
||||
|
||||
void CAscReader::ProcessSample(const std::string& rssSample)
|
||||
{
|
||||
std::istringstream sstream(rssSample);
|
||||
|
||||
SCanMessage sMsg{};
|
||||
|
||||
// Expecting a time
|
||||
if (!(sstream >> sMsg.dTimestamp))
|
||||
return; // All usable mesaurements must start with a timestamp
|
||||
|
||||
// Skip whitespace
|
||||
while (std::isspace(sstream.peek())) sstream.get();
|
||||
|
||||
// If the next timestamp is followed by the "CANFD" keyword, the sample is a CAN-FD sample
|
||||
if (std::isalpha(sstream.peek()))
|
||||
{
|
||||
std::string ssKeyword;
|
||||
if (!(sstream >> ssKeyword))
|
||||
return; // Unexpected
|
||||
if (ssKeyword != "CANFD")
|
||||
return; // Not a CAN-FD sample
|
||||
sMsg.bCanFd = true;
|
||||
|
||||
// Expecting a channel
|
||||
if (!(sstream >> sMsg.uiChannel))
|
||||
return; // Expected a channel (otherwise the measurement might contain meta data)
|
||||
|
||||
// Determine the direction
|
||||
std::string ssDirection;
|
||||
if (!(sstream >> ssDirection))
|
||||
return; // Expected a direction
|
||||
if (ssDirection == "Rx")
|
||||
sMsg.eDirection = SCanMessage::EDirection::rx;
|
||||
else if (ssDirection == "Tx")
|
||||
sMsg.eDirection = SCanMessage::EDirection::tx;
|
||||
else
|
||||
return; // Invalid direction
|
||||
|
||||
// Expecting a message ID in hex format
|
||||
if (!(sstream >> std::hex >> sMsg.uiId))
|
||||
return; // Expected an ID (otherwise the measurement might contain meta data)
|
||||
|
||||
// Skip whitespace
|
||||
while (std::isspace(sstream.peek())) sstream.get();
|
||||
|
||||
// Optional 'x' for an extended ID
|
||||
if (std::tolower(sstream.peek()) == 'x')
|
||||
{
|
||||
sMsg.bExtended = true;
|
||||
sstream.get(); // Skip the character, since already processed
|
||||
}
|
||||
|
||||
// Get bit rate switch (BRS)
|
||||
size_t nBRS = 0;
|
||||
if (!(sstream >> nBRS) || nBRS > 1)
|
||||
return; // Unexpected or invalid BRS
|
||||
|
||||
// Get error state indicator (ESI)
|
||||
size_t nESI = 0;
|
||||
if (!(sstream >> nESI) || nESI > 1)
|
||||
return; // Unexpected or invalid ESI
|
||||
|
||||
// Get data length code (DLC)
|
||||
size_t nDLC;
|
||||
if (!(sstream >> nDLC) || nDLC > 15)
|
||||
return; // Unexpected or invalid DLC
|
||||
|
||||
// Get the data length
|
||||
if (!(sstream >> std::dec >> sMsg.uiLength) || sMsg.uiLength > 64)
|
||||
return; // Length expected or invalid length
|
||||
|
||||
// Check for proper DLC vs. length
|
||||
const size_t rgnDLCLength[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64 };
|
||||
if (rgnDLCLength[nDLC] != sMsg.uiLength) return; // Invalid length
|
||||
}
|
||||
else // standard CAN
|
||||
{
|
||||
// Expecting a channel
|
||||
if (!(sstream >> sMsg.uiChannel))
|
||||
return; // Expected a channel (otherwise the measurement might contain meta data)
|
||||
|
||||
// Expecting a message ID in hex format
|
||||
if (!(sstream >> std::hex >> sMsg.uiId))
|
||||
return; // Expected an ID (otherwise the measurement might contain meta data)
|
||||
|
||||
// Skip whitespace
|
||||
while (std::isspace(sstream.peek())) sstream.get();
|
||||
|
||||
// Optional 'x' for an extended ID
|
||||
if (std::tolower(sstream.peek()) == 'x')
|
||||
{
|
||||
sMsg.bExtended = true;
|
||||
sstream.get(); // Skip the character, since already processed
|
||||
}
|
||||
|
||||
// Determine the direction
|
||||
std::string ssDirection;
|
||||
if (!(sstream >> ssDirection))
|
||||
return; // Expected a direction
|
||||
if (ssDirection == "Rx")
|
||||
sMsg.eDirection = SCanMessage::EDirection::rx;
|
||||
else if (ssDirection == "Tx")
|
||||
sMsg.eDirection = SCanMessage::EDirection::tx;
|
||||
else
|
||||
return; // Invalid direction
|
||||
|
||||
// Determine whether the measurement contains data from a data frame (remote frames are not processed).
|
||||
char cLocation = '\0';
|
||||
if (!(sstream >> cLocation) || std::tolower(cLocation) != 'd')
|
||||
return; // Not supported location
|
||||
|
||||
// Get the data length
|
||||
if (!(sstream >> std::dec >> sMsg.uiLength) || sMsg.uiLength > 8)
|
||||
return; // Length expected or invalid length
|
||||
}
|
||||
|
||||
// Read the data
|
||||
for (uint32_t uiIndex = 0; uiIndex < sMsg.uiLength; uiIndex++)
|
||||
{
|
||||
uint32_t uiVal = 0;
|
||||
if (!(sstream >> std::hex >> uiVal))
|
||||
return; // Expected data byte
|
||||
sMsg.rguiData[uiIndex] = static_cast<uint8_t>(uiVal);
|
||||
}
|
||||
|
||||
// Add the message to the vector
|
||||
m_lstMessages.push_back(std::move(sMsg));
|
||||
|
||||
// Ignore the rest of the line (additional information could have been logged).
|
||||
}
|
||||
|
||||
void CAscReader::PlaybackThreadFunc(std::function<void(const SCanMessage&)> fnCallback, std::atomic<uint32_t>& loopCount, bool bRepeat)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
||||
#elif defined __unix__
|
||||
pthread_attr_t thAttr;
|
||||
pthread_attr_init(&thAttr);
|
||||
int iPolicy = 0;
|
||||
pthread_attr_getschedpolicy(&thAttr, &iPolicy);
|
||||
int iMaxPrioForPolicy = sched_get_priority_max(iPolicy);
|
||||
pthread_setschedprio(pthread_self(), iMaxPrioForPolicy);
|
||||
pthread_attr_destroy(&thAttr);
|
||||
#endif
|
||||
|
||||
// Indicate that playback thread has started.
|
||||
m_bPlaybackThread = true;
|
||||
// when data set is repeated, we need the offset compared to the first run
|
||||
double dLoopOffset = 0.00000;
|
||||
|
||||
// Define the offset
|
||||
double dOffset = 0.00000;
|
||||
if (!IsBOF())
|
||||
{
|
||||
operator--();
|
||||
auto prSample = Get();
|
||||
if (!prSample.second)
|
||||
{
|
||||
m_bPlayback = false;
|
||||
std::cout << "ASC PLAYBACK: Unexpected situation. Not BOF, but also no previous sample." << std::endl;
|
||||
return; // Should not occur
|
||||
}
|
||||
dOffset = prSample.first.dTimestamp;
|
||||
operator++();
|
||||
}
|
||||
|
||||
loopCount++;
|
||||
auto timepointStartOfFirstLoop = std::chrono::high_resolution_clock::now();
|
||||
auto timepointStart = std::chrono::high_resolution_clock::now();
|
||||
while (m_bPlayback)
|
||||
{
|
||||
if (IsEOF())
|
||||
{
|
||||
if (!bRepeat) break; // Done
|
||||
|
||||
// Data set will be repeated, callculate offset compared to the first run
|
||||
auto timepointStartOfNextLoop = std::chrono::high_resolution_clock::now();
|
||||
dLoopOffset = std::chrono::duration<double>(timepointStartOfNextLoop - timepointStartOfFirstLoop).count();
|
||||
|
||||
// Restart
|
||||
loopCount++;
|
||||
dOffset = 0.00000;
|
||||
JumpBegin();
|
||||
}
|
||||
|
||||
// Get a sample
|
||||
auto prSample = Get();
|
||||
if (!prSample.second)
|
||||
break; // Should not occur
|
||||
|
||||
// Need to sleep?
|
||||
while (m_bPlayback)
|
||||
{
|
||||
// Determine the current time relative to start time.
|
||||
auto timepoint = std::chrono::high_resolution_clock::now();
|
||||
double dTime = std::chrono::duration<double>(timepoint - timepointStart).count() + dOffset;
|
||||
|
||||
// If the sample is overdue... do not sleep
|
||||
if ((prSample.first.dTimestamp + dLoopOffset) < dTime)
|
||||
break;
|
||||
|
||||
// If the sample will be overdue in 10 ms, yield the current thread only (this will not put the thread in idle mode).
|
||||
//if (prSample.first.dTimestamp < dTime + 0.010)
|
||||
// std::this_thread::yield();
|
||||
//else // Sleep shortly... this might cause a sleep of more than 1ms dependable on the load of the system
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(0));
|
||||
}
|
||||
|
||||
// Send the sample
|
||||
fnCallback(prSample.first);
|
||||
|
||||
// Next sample
|
||||
operator++();
|
||||
}
|
||||
|
||||
m_bPlayback = false;
|
||||
}
|
||||
} // namespace asc
|
||||
162
global/ascformat/ascreader.h
Normal file
162
global/ascformat/ascreader.h
Normal file
@@ -0,0 +1,162 @@
|
||||
#ifndef ASC_FILE_READER_H
|
||||
#define ASC_FILE_READER_H
|
||||
|
||||
#include <list>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
|
||||
namespace asc
|
||||
{
|
||||
/**
|
||||
* @brief CAN message structure
|
||||
*/
|
||||
struct SCanMessage
|
||||
{
|
||||
double dTimestamp; ///< Timestamp in seconds
|
||||
uint32_t uiChannel; ///< CAN channel (first channel starts with 0).
|
||||
uint32_t uiId; ///< CAN ID
|
||||
bool bExtended; ///< Set when the CAN ID is extended (29 bits) or not when standard
|
||||
///< (11 bits).
|
||||
bool bCanFd; ///< Set when the message contains CAN-FD data (more than 8 bytes).
|
||||
/// Direction enumeration
|
||||
enum class EDirection { rx, tx } eDirection; ///< Direction RX or TX.
|
||||
uint32_t uiLength; ///< Length of the CAN data.
|
||||
uint8_t rguiData[64]; ///< Array with the CAN data.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This class allows reading the Vector ASC file format.
|
||||
* @attention This class assumes no concurrency between reading a file, navigation and playback. No thread synchronization is
|
||||
* implemented.
|
||||
*/
|
||||
class CAscReader
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CAscReader();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CAscReader();
|
||||
|
||||
/**
|
||||
* @brief Read a file (this will replace the current samples with the samples of the file).
|
||||
* @param[in] rpathFile Reference to the file path.
|
||||
* @return Returns 'true' on success or 'false' when not.
|
||||
*/
|
||||
bool Read(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Get the current sample. If the current position is EOF, no sample is returned.
|
||||
* @return A pair consisting of the current sample and a boolean indicating that the sample is valid.
|
||||
*/
|
||||
std::pair<SCanMessage, bool> Get() const;
|
||||
|
||||
/**
|
||||
* @brief Get the number of loops the data set was sent
|
||||
* @return Number of loops the data set was sent including the current loop
|
||||
*/
|
||||
uint32_t GetLoopCount() const;
|
||||
|
||||
/**
|
||||
* @brief Jump to the first sample.
|
||||
*/
|
||||
void JumpBegin();
|
||||
|
||||
/**
|
||||
* @brief Jump beyond the last sample.
|
||||
*/
|
||||
void JumpEnd();
|
||||
|
||||
/**
|
||||
* @{
|
||||
* @brief Increase the current position to the next sample.
|
||||
* @return Reference to this class.
|
||||
*/
|
||||
CAscReader& operator++();
|
||||
CAscReader& operator++(int);
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @{
|
||||
* @brief Decrease the current position to the previous sample.
|
||||
* @return Reference to this class.
|
||||
*/
|
||||
CAscReader& operator--();
|
||||
CAscReader& operator--(int);
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Does the current position point to the first sample?
|
||||
* @return Returns whether the current position points to the first sample.
|
||||
*/
|
||||
bool IsBOF() const;
|
||||
|
||||
/**
|
||||
* @brief Does the current position point beyond the last sample?
|
||||
* @return Returns whether the current position points beyond the last sample.
|
||||
*/
|
||||
bool IsEOF() const;
|
||||
|
||||
/**
|
||||
* @brief Start playback using the timestamp of the samples to come as close to the original recording time.
|
||||
* @param[in] fnCallback The function being called with the current sample.
|
||||
* @param[in] bRepeat When set, the playback continuous at the beginning when reaching the end of the data set.
|
||||
*/
|
||||
void StartPlayback(std::function<void(const SCanMessage&)> fnCallback, bool bRepeat = true);
|
||||
|
||||
/**
|
||||
* @brief Stop the current playback.
|
||||
* @remarks This does not change the current position (pointing to the next sample following the one just sent.
|
||||
*/
|
||||
void StopPlayback();
|
||||
|
||||
/**
|
||||
* @brief Reset the playback to the beginning of the data set. This is equal to calling StopPlayback and JumpBegin.
|
||||
*/
|
||||
void ResetPlayback();
|
||||
|
||||
/**
|
||||
* @brief Returns whether playback is running at the moment.
|
||||
* @return Returns whether playback is running.
|
||||
*/
|
||||
bool PlaybackRunning() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Process a measurement value sample (one line within the ASC trigger block section).
|
||||
* @details The ASC measurement uses the following format for one CAN measurement value:
|
||||
*
|
||||
* \verbatim
|
||||
* For CAN: Timestamp Busnumber CanId ["x"] "Rx|Tx" "d" Number-of-bytes Byte1, Byte2, ..., Byte8 [...]
|
||||
* For CAN-FD: Timestamp CANFD <channel> <dir> <id> <brs> <esi> <dlc> <data_len> <data> [ ... ]
|
||||
* \endverbatim
|
||||
*
|
||||
* @param[in] rssSample Reference to the measurement value sample.
|
||||
*/
|
||||
void ProcessSample(const std::string& rssSample);
|
||||
|
||||
/**
|
||||
* @brief Playback thread function. If the current position is BOF, the playback starts from time = 0.0000, otherwise the
|
||||
* playback starts from the current position.
|
||||
*/
|
||||
void PlaybackThreadFunc(std::function<void(const SCanMessage&)> fnCallback, std::atomic<uint32_t>& loopCount, bool bRepeat);
|
||||
|
||||
std::list<SCanMessage> m_lstMessages; ///< Vector with messages
|
||||
std::list<SCanMessage>::iterator m_itCurrent; ///< Current iterator position
|
||||
std::thread m_threadPlayback; ///< Playback thread.
|
||||
bool m_bPlaybackThread = false; ///< Set when running playback thread
|
||||
bool m_bPlayback = false; ///< Set when running playback
|
||||
std::atomic<uint32_t> m_uiLoopCount{ 0 }; ///< Counter how often the data set was sent
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !defined ASC_FILE_READER_H
|
||||
96
global/ascformat/ascwriter.cpp
Normal file
96
global/ascformat/ascwriter.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "ascwriter.h"
|
||||
#include <fstream>
|
||||
|
||||
namespace asc
|
||||
{
|
||||
CAscWriter::CAscWriter()
|
||||
{
|
||||
StartTimer();
|
||||
}
|
||||
|
||||
void CAscWriter::AddSample(const SCanMessage& rsSample)
|
||||
{
|
||||
SCanMessage sSampleCopy(rsSample);
|
||||
if (sSampleCopy.dTimestamp == 0.0000000)
|
||||
sSampleCopy.dTimestamp =
|
||||
std::chrono::duration<double>(std::chrono::high_resolution_clock::now() - m_timepointStart).count();
|
||||
else
|
||||
{
|
||||
if (!m_lstMessages.empty() && m_lstMessages.back().dTimestamp > sSampleCopy.dTimestamp) return;
|
||||
}
|
||||
m_lstMessages.push_back(std::move(sSampleCopy));
|
||||
}
|
||||
|
||||
bool CAscWriter::HasSamples() const
|
||||
{
|
||||
return !m_lstMessages.empty();
|
||||
}
|
||||
|
||||
void CAscWriter::StartTimer()
|
||||
{
|
||||
if (m_lstMessages.empty())
|
||||
{
|
||||
m_timepointStart = std::chrono::high_resolution_clock::now();
|
||||
m_timepointSystem = std::chrono::system_clock::now();
|
||||
}
|
||||
}
|
||||
|
||||
void CAscWriter::Clear()
|
||||
{
|
||||
m_lstMessages.clear();
|
||||
m_timepointStart = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
bool CAscWriter::Write(const std::filesystem::path& rpathFile)
|
||||
{
|
||||
// Open and write the file
|
||||
std::ofstream fstream(rpathFile, std::ios::out | std::ios::trunc);
|
||||
if (!fstream.is_open()) return false;
|
||||
|
||||
// Fill in header
|
||||
time_t tmSystemTime = std::chrono::system_clock::to_time_t(m_timepointSystem);
|
||||
fstream << "date " << std::put_time(std::localtime(&tmSystemTime), "%c") << std::endl;
|
||||
fstream << "base hex timestamps absolute" << std::endl;
|
||||
|
||||
// Stream trigger block
|
||||
fstream << "Begin Triggerblock " << std::put_time(std::localtime(&tmSystemTime), "%c") << std::endl;
|
||||
fstream << " 0.000000 Start of measurement" << std::endl;
|
||||
for (const SCanMessage& rsSample : m_lstMessages)
|
||||
{
|
||||
std::stringstream sstreamTimestamp;
|
||||
sstreamTimestamp << std::fixed << std::setprecision(6) << rsSample.dTimestamp;
|
||||
fstream << std::string(11ull - sstreamTimestamp.str().length(), ' ') << sstreamTimestamp.str();
|
||||
if (rsSample.bCanFd)
|
||||
{
|
||||
fstream << " " << "CANFD" << " " << rsSample.uiChannel << " " <<
|
||||
(rsSample.eDirection == SCanMessage::EDirection::rx ? "Rx" : "Tx") <<
|
||||
std::hex << std::uppercase << rsSample.uiId << (rsSample.bExtended ? "x" : "") << " 1 0 ";
|
||||
const size_t rgnDLCLength[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64 };
|
||||
size_t nDLC = 0;
|
||||
for (size_t n = 0; n < 16; n++)
|
||||
if (rgnDLCLength[n] == rsSample.uiLength) nDLC = n;
|
||||
fstream << nDLC << " " << rsSample.uiLength;
|
||||
}
|
||||
else // Normal CAN
|
||||
{
|
||||
fstream << " " << rsSample.uiChannel;
|
||||
std::stringstream sstreamID;
|
||||
sstreamID << std::hex << std::uppercase << rsSample.uiId;
|
||||
fstream << " " << sstreamID.str() << (rsSample.bExtended ? "x" : "");
|
||||
fstream << std::string(16ull - sstreamID.str().length(), ' ') <<
|
||||
(rsSample.eDirection == SCanMessage::EDirection::rx ? "Rx" : "Tx");
|
||||
fstream << " " << "d";
|
||||
fstream << " " << rsSample.uiLength;
|
||||
}
|
||||
for (uint32_t uiIndex = 0; uiIndex < rsSample.uiLength; uiIndex++)
|
||||
fstream << " " << std::hex << std::setw(2) << std::setfill('0') << std::uppercase << static_cast<size_t>(rsSample.rguiData[uiIndex]);
|
||||
fstream << std::endl;
|
||||
}
|
||||
fstream << "End TriggerBlock" << std::endl;
|
||||
|
||||
// Done
|
||||
fstream.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace asc
|
||||
63
global/ascformat/ascwriter.h
Normal file
63
global/ascformat/ascwriter.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef ASC_FILE_WRITER_H
|
||||
#define ASC_FILE_WRITER_H
|
||||
|
||||
#include "ascreader.h"
|
||||
#include <list>
|
||||
#include <chrono>
|
||||
|
||||
namespace asc
|
||||
{
|
||||
/**
|
||||
* @brief This class allows writing the Vector ASC file format.
|
||||
* @attention This class assumes no concurrency between writing the file and adding samples. No thread synchronization is
|
||||
* implemented.
|
||||
*/
|
||||
class CAscWriter
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CAscWriter();
|
||||
|
||||
/**
|
||||
* @brief Start the timer for automatic timestamp creation.
|
||||
* @remarks Resets the start time only if there are no samples are available.
|
||||
*/
|
||||
void StartTimer();
|
||||
|
||||
/**
|
||||
* @brief Add a sample.
|
||||
* @attention The sample will not be added if the timestamp is smaller than the last sample.
|
||||
* @param[in] rsSample Reference to the CAN sample structure. If the timestamp is empty, the timestamp is created automatically
|
||||
* using the integrated timer.
|
||||
*/
|
||||
void AddSample(const SCanMessage& rsSample);
|
||||
|
||||
/**
|
||||
* @brief Returns whether at least one sample is stored.
|
||||
* @return Returns 'true' when samples are available; 'false' when not.
|
||||
*/
|
||||
bool HasSamples() const;
|
||||
|
||||
/**
|
||||
* @brief Clear the content.
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* @brief Write to a file (the file gets overwritten if existing).
|
||||
* @param[in] rpathFile Reference to the file path.
|
||||
* @return Returns 'true' on success or 'false' when not.
|
||||
*/
|
||||
bool Write(const std::filesystem::path& rpathFile);
|
||||
|
||||
private:
|
||||
std::list<SCanMessage> m_lstMessages; ///< Queue with messages
|
||||
std::chrono::high_resolution_clock::time_point m_timepointStart; ///< Starting timepoint for automatic timestamp
|
||||
///< generation.
|
||||
std::chrono::system_clock::time_point m_timepointSystem; ///< Starting timepoint for time/date generation.
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !defined ASC_FILE_WRITER_H
|
||||
306
global/base64.h
Normal file
306
global/base64.h
Normal file
@@ -0,0 +1,306 @@
|
||||
#ifndef BASE64_H
|
||||
#define BASE64_H
|
||||
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
static const std::string Base64Chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
/**
|
||||
* @brief check whether its a Base64 format
|
||||
* @param[in] c character to be looked for
|
||||
* @return true if Base64 format
|
||||
*/
|
||||
inline bool IsBase64(const unsigned char c)
|
||||
{
|
||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Encode to Base64 format
|
||||
* @param[in] plainText text to be encoded to Base64
|
||||
* @return text encoded to format
|
||||
*/
|
||||
inline std::string Base64EncodePlainText(const std::string& plainText)
|
||||
{
|
||||
const char* buffer = plainText.c_str();
|
||||
std::size_t bytesLeft = plainText.size();
|
||||
|
||||
std::string ret;
|
||||
int i = 0;
|
||||
unsigned char char_array_3[3]{ 0 };
|
||||
unsigned char char_array_4[4]{ 0 };
|
||||
|
||||
while (bytesLeft--)
|
||||
{
|
||||
char_array_3[i++] = *reinterpret_cast<const unsigned char*>((buffer++));
|
||||
if (i == 3)
|
||||
{
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (i = 0; (i < 4); i++)
|
||||
{
|
||||
ret += Base64Chars[char_array_4[i]];
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i)
|
||||
{
|
||||
for (int j = i; j < 3; j++)
|
||||
{
|
||||
char_array_3[j] = '\0';
|
||||
}
|
||||
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (int j = 0; (j < i + 1); j++)
|
||||
{
|
||||
ret += Base64Chars[char_array_4[j]];
|
||||
}
|
||||
|
||||
while ((i++ < 3))
|
||||
{
|
||||
ret += '=';
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Decode Base64 formatstring
|
||||
* @param[in] encoded_string to be decoded
|
||||
* @return decoded string
|
||||
*/
|
||||
inline std::string Base64DecodePlainText(const std::string& encoded_string)
|
||||
{
|
||||
size_t in_len = encoded_string.size();
|
||||
size_t i = 0;
|
||||
size_t in_ = 0;
|
||||
unsigned char char_array_4[4], char_array_3[3];
|
||||
std::string ret;
|
||||
|
||||
while (in_len-- && (encoded_string[in_] != '=') && IsBase64(encoded_string[in_]))
|
||||
{
|
||||
char_array_4[i++] = encoded_string[in_]; in_++;
|
||||
if (i == 4)
|
||||
{
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
char_array_4[i] = static_cast<uint8_t>(Base64Chars.find(char_array_4[i]));
|
||||
}
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (i = 0; (i < 3); i++)
|
||||
{
|
||||
ret+=(reinterpret_cast<char*>(char_array_3)[i]);
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i)
|
||||
{
|
||||
for (size_t j = i; j < 4; j++)
|
||||
{
|
||||
char_array_4[j] = 0;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < 4; j++)
|
||||
{
|
||||
char_array_4[j] = static_cast<uint8_t>(Base64Chars.find(char_array_4[j]));
|
||||
}
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (size_t j = 0; (j < i - 1); j++)
|
||||
{
|
||||
ret += (reinterpret_cast<char*>(char_array_3)[j]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Encode to Base64 format
|
||||
* @param[in] data buffer to be encoded to Base64
|
||||
* @param[in] datalength buffer length to be encoded to Base64
|
||||
* @return text encoded to format
|
||||
*/
|
||||
inline std::string Base64Encode(const char* data, std::size_t datalength)
|
||||
{
|
||||
const char* buffer = data;
|
||||
std::size_t bytesLeft = datalength;
|
||||
|
||||
std::string ret;
|
||||
int i = 0;
|
||||
unsigned char char_array_3[3]{ 0 };
|
||||
unsigned char char_array_4[4]{ 0 };
|
||||
|
||||
while (bytesLeft--)
|
||||
{
|
||||
char_array_3[i++] = *(buffer++);
|
||||
if (i == 3)
|
||||
{
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (i = 0; (i < 4); i++)
|
||||
{
|
||||
ret += Base64Chars[char_array_4[i]];
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i)
|
||||
{
|
||||
for (int j = i; j < 3; j++)
|
||||
{
|
||||
char_array_3[j] = '\0';
|
||||
}
|
||||
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (int j = 0; (j < i + 1); j++)
|
||||
{
|
||||
ret += Base64Chars[char_array_4[j]];
|
||||
}
|
||||
|
||||
while ((i++ < 3))
|
||||
{
|
||||
ret += '=';
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode Base64 buffer
|
||||
* @param[in] data buffer to be encoded to Base64
|
||||
* @return decoded string
|
||||
*/
|
||||
template <class TConvert>
|
||||
inline std::string Base64Encode(const TConvert& data)
|
||||
{
|
||||
return Base64Encode(reinterpret_cast<const char*>(&data), sizeof(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode Base64 formatstring
|
||||
* @param[in] encoded_string to be decoded
|
||||
* @return decoded string
|
||||
*/
|
||||
inline std::vector<unsigned char> Base64Decode(const std::string& encoded_string)
|
||||
{
|
||||
size_t in_len = encoded_string.size();
|
||||
size_t i = 0;
|
||||
size_t in_ = 0;
|
||||
unsigned char char_array_4[4], char_array_3[3];
|
||||
std::vector<unsigned char> ret;
|
||||
|
||||
while (in_len-- && (encoded_string[in_] != '=') && IsBase64(encoded_string[in_]))
|
||||
{
|
||||
char_array_4[i++] = encoded_string[in_]; in_++;
|
||||
if (i == 4)
|
||||
{
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
char_array_4[i] = static_cast<uint8_t>(Base64Chars.find(char_array_4[i]));
|
||||
}
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (i = 0; (i < 3); i++)
|
||||
{
|
||||
ret.push_back(char_array_3[i]);
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i)
|
||||
{
|
||||
for (size_t j = i; j < 4; j++)
|
||||
{
|
||||
char_array_4[j] = 0;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < 4; j++)
|
||||
{
|
||||
char_array_4[j] = static_cast<uint8_t>(Base64Chars.find(char_array_4[j]));
|
||||
}
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (size_t j = 0; (j < i - 1); j++)
|
||||
{
|
||||
ret.push_back(char_array_3[j]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
// memcpy_s(&datablock, sizeof(datablock), ret.data(), ret.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode Base64 formatstring
|
||||
* @param[in] encoded_string to be decoded
|
||||
* @return decoded string
|
||||
*/
|
||||
template <class TConvert>
|
||||
inline TConvert DecodeBase64(const std::string& encoded_string)
|
||||
{
|
||||
TConvert out{};
|
||||
std::vector<unsigned char> rawData = Base64Decode(encoded_string);
|
||||
if (sizeof(out) == rawData.size())
|
||||
{
|
||||
memcpy_s(&out, sizeof(out), rawData.data(), rawData.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
TConvert invalid{};
|
||||
return invalid;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode Base64 formatstring
|
||||
* @param[in] encoded_string to be decoded
|
||||
* @return decoded string
|
||||
*/
|
||||
template <>
|
||||
inline std::string DecodeBase64(const std::string& encoded_string)
|
||||
{
|
||||
std::vector<unsigned char> rawData = Base64Decode(encoded_string);
|
||||
std::string out(reinterpret_cast<char*>(rawData.data()), rawData.size());
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
726
global/cmdlnparser/cmdlnparser.cpp
Normal file
726
global/cmdlnparser/cmdlnparser.cpp
Normal file
@@ -0,0 +1,726 @@
|
||||
#include "cmdlnparser.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#pragma push_macro("interface")
|
||||
#ifdef interface
|
||||
#undef interface
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
#pragma pop_macro("interface")
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <linux/limits.h>
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
CArgumentDefBase::~CArgumentDefBase()
|
||||
{
|
||||
}
|
||||
|
||||
bool CArgumentDefBase::CompareNameAndAssign(CArgumentIterator& rargit, const std::string& rssArgument,
|
||||
const std::string& rssOptionName, bool bPartial) const
|
||||
{
|
||||
if (!m_ptrArgProvide) return false; // No variable definition assigned.
|
||||
|
||||
// Determine wherther the supplied argument is an option or a sub-option
|
||||
size_t nPos = 0;
|
||||
bool bOption = false;
|
||||
bool bSubOption = false;
|
||||
if (rssArgument.size() >= 2 && rssArgument[0] == '-' && rssArgument[1] == '-')
|
||||
{
|
||||
bSubOption = true;
|
||||
nPos += 2;
|
||||
}
|
||||
else if (rssArgument.size() >= 1)
|
||||
{
|
||||
if (rssArgument[0] == '-')
|
||||
bOption = true;
|
||||
#ifdef _WIN32
|
||||
if (rssArgument[0] == '/')
|
||||
bOption = true;
|
||||
#endif
|
||||
if (bOption) nPos++;
|
||||
}
|
||||
|
||||
// Differentiate between default and option arguments... default arguments do not have a name
|
||||
if (!rssOptionName.empty()) // Option
|
||||
{
|
||||
if (!bOption && !bSubOption) return false;
|
||||
|
||||
// Determine the first non-alpha-numeric character that is not part of the name.
|
||||
size_t nNameStartPos = nPos;
|
||||
std::string ssArgNameCS, ssArgNameCI;
|
||||
while (nPos < rssArgument.size())
|
||||
{
|
||||
char c = rssArgument[nPos];
|
||||
if (!std::isalnum(c) && c != '_' && c != '?')
|
||||
break;
|
||||
ssArgNameCS += c;
|
||||
ssArgNameCI += static_cast<char>(std::tolower(c));
|
||||
nPos++;
|
||||
}
|
||||
|
||||
// Check for correct length
|
||||
if (!bPartial && rssOptionName.size() != (nPos - nNameStartPos))
|
||||
return false;
|
||||
|
||||
// Check the name
|
||||
bool bFound = false;
|
||||
std::string ssArgDefNameCI = rssOptionName;
|
||||
for (char& rc : ssArgDefNameCI) rc = static_cast<char>(std::tolower(rc));
|
||||
if (m_rCLParser.CheckParseFlag(CCommandLine::EParseFlags::assignment_next_arg) || CheckFlag(EArgumentFlags::flag_option) ||
|
||||
CheckFlag(EArgumentFlags::bool_option))
|
||||
{
|
||||
// Full fit needed
|
||||
bFound |= CheckFlag(EArgumentFlags::case_sensitive) ?
|
||||
ssArgNameCS == rssOptionName :
|
||||
ssArgNameCI == ssArgDefNameCI;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Partial fit allowed
|
||||
size_t nMaxNameLen = bPartial ? rssOptionName.size() : (nPos - nNameStartPos);
|
||||
bFound |= CheckFlag(EArgumentFlags::case_sensitive) ?
|
||||
ssArgNameCS.substr(0, nMaxNameLen) == rssOptionName :
|
||||
ssArgNameCI.substr(0, nMaxNameLen) == ssArgDefNameCI;
|
||||
if (bFound)
|
||||
nPos = nNameStartPos + nMaxNameLen;
|
||||
}
|
||||
|
||||
// Found? If not, jump out of here
|
||||
if (!bFound) return false;
|
||||
}
|
||||
|
||||
// Get the value
|
||||
std::string ssValue;
|
||||
if (m_rCLParser.CheckParseFlag(CCommandLine::EParseFlags::assignment_next_arg) && !CheckFlag(EArgumentFlags::flag_option))
|
||||
{
|
||||
if (!CheckFlag(EArgumentFlags::bool_option))
|
||||
{
|
||||
auto optArg = rargit.GetNext();
|
||||
if (!optArg)
|
||||
{
|
||||
SArgumentParseException exception("Missing arument value!");
|
||||
exception.AddIndex(rargit.GetIndexOfLastArg());
|
||||
exception.AddArgument(rssArgument);
|
||||
throw exception;
|
||||
}
|
||||
ssValue = *optArg;
|
||||
}
|
||||
}
|
||||
else
|
||||
ssValue = helper::trim(rssArgument.substr(nPos));
|
||||
|
||||
// Check whether the argument was previously assigned and doesn't allow multiple assignments.
|
||||
if (m_ptrArgProvide->IsArgumentAssigned() && !m_ptrArgProvide->AllowMultiArgumentAssign())
|
||||
throw SArgumentParseException("Cannot supply more than one value for the option/argument!");
|
||||
|
||||
// Assign.
|
||||
m_ptrArgProvide->ArgumentAssign(ssValue);
|
||||
|
||||
// Option is available at the command line.
|
||||
m_bAvailableOnCommandLine = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CArgumentDefBase::AddExample(const std::string& rssExample)
|
||||
{
|
||||
if (rssExample.empty()) return;
|
||||
m_lstExamples.push_back(rssExample);
|
||||
}
|
||||
|
||||
CCommandLine::CCommandLine(uint32_t uiFlags /* = static_cast<uint32_t>(EParseFlags::assignment_character)*/) :
|
||||
m_uiParseFlags(uiFlags)
|
||||
{}
|
||||
|
||||
CCommandLine::~CCommandLine()
|
||||
{}
|
||||
|
||||
std::filesystem::path CCommandLine::GetApplicationPath() const
|
||||
{
|
||||
std::filesystem::path path;
|
||||
#ifdef _WIN32
|
||||
wchar_t szAppPath[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, szAppPath, MAX_PATH);
|
||||
path = szAppPath;
|
||||
#else
|
||||
char szAppPath[PATH_MAX];
|
||||
ssize_t nCount = readlink("/proc/self/exe", szAppPath, PATH_MAX);
|
||||
path = std::string(szAppPath, (nCount > 0) ? nCount : 0);
|
||||
#endif
|
||||
return path;
|
||||
}
|
||||
|
||||
void CCommandLine::DefineGroup(const std::string& rssTitle, const std::string& rssDescription /*= std::string{}*/)
|
||||
{
|
||||
if (rssTitle.empty()) throw SArgumentParseException("No group title provided!");
|
||||
m_ptrCurrentGroup = std::make_shared<SGroupDef>();
|
||||
m_ptrCurrentGroup->ssTitle = rssTitle;
|
||||
if (!rssDescription.empty()) m_ptrCurrentGroup->ssDescription = rssDescription;
|
||||
}
|
||||
|
||||
void CCommandLine::PrintFixedWidth(size_t nWidth)
|
||||
{
|
||||
m_nFixedWidth = nWidth;
|
||||
}
|
||||
|
||||
size_t CCommandLine::PrintFixedWidth() const
|
||||
{
|
||||
return m_nFixedWidth;
|
||||
}
|
||||
|
||||
void CCommandLine::PrintMaxWidth(size_t nWidth)
|
||||
{
|
||||
m_nFixedWidth = 0;
|
||||
m_nMaxWidth = nWidth;
|
||||
}
|
||||
|
||||
size_t CCommandLine::PrintMaxWidth() const
|
||||
{
|
||||
return m_nFixedWidth ? m_nFixedWidth : m_nMaxWidth;
|
||||
}
|
||||
|
||||
void CCommandLine::PrintSyntax(bool bEnable)
|
||||
{
|
||||
m_bSyntaxPrint = bEnable;
|
||||
}
|
||||
|
||||
bool CCommandLine::PrintSyntax() const
|
||||
{
|
||||
return m_bSyntaxPrint;
|
||||
}
|
||||
|
||||
void CCommandLine::PrintHelp(std::ostream& rstream, const std::string& rssHelpText /*= std::string{}*/,
|
||||
size_t nArgumentGroup /*= 0*/) const
|
||||
{
|
||||
// Auto detection of the printable width?
|
||||
size_t nPrintWidth = m_nFixedWidth;
|
||||
if (!nPrintWidth)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CONSOLE_SCREEN_BUFFER_INFO sScreenBufferInfo = {};
|
||||
if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &sScreenBufferInfo))
|
||||
nPrintWidth = static_cast<size_t>(sScreenBufferInfo.srWindow.Right) - static_cast<size_t>(sScreenBufferInfo.srWindow.Left) + 1;
|
||||
#else
|
||||
struct winsize ws;
|
||||
ioctl(0, TIOCGWINSZ, &ws);
|
||||
nPrintWidth = ws.ws_col;
|
||||
#endif
|
||||
|
||||
if (m_nMaxWidth > 0) // ignore if it's 0
|
||||
{
|
||||
// Max width?
|
||||
if (nPrintWidth > m_nMaxWidth)
|
||||
nPrintWidth = m_nMaxWidth;
|
||||
}
|
||||
|
||||
// Minimum is fixed...
|
||||
if (nPrintWidth < 40)
|
||||
nPrintWidth = 40;
|
||||
}
|
||||
|
||||
// Print the help text if provided.
|
||||
if (!rssHelpText.empty()) PrintHelpText(rstream, rssHelpText, nPrintWidth);
|
||||
|
||||
// Get the help text of the default argument
|
||||
std::string ssDefaultArg;
|
||||
if (m_ptrDefaultArg)
|
||||
ssDefaultArg = m_ptrDefaultArg->GetHelpText();
|
||||
|
||||
// Print the syntax
|
||||
size_t nOptionCnt = m_lstOptionArgs.size();
|
||||
if (m_bSyntaxPrint)
|
||||
{
|
||||
rstream << std::endl << "Syntax: " << GetApplicationPath().filename().u8string();
|
||||
if (!ssDefaultArg.empty())
|
||||
rstream << " <" << ssDefaultArg << ">";
|
||||
if (nOptionCnt)
|
||||
rstream << " [options...]";
|
||||
rstream << std::endl;
|
||||
}
|
||||
|
||||
// Print the options
|
||||
if (nOptionCnt)
|
||||
{
|
||||
// Determine the length of the longest argument
|
||||
size_t nStartOfTextBlock = 0;
|
||||
auto fnLongestArgName = [&](const std::shared_ptr<CArgumentDefBase>& rptrArgument) -> void
|
||||
{
|
||||
// Make a combined argument from argument parts
|
||||
std::string ssArgument;
|
||||
for (const CArgumentDefBase::SOptionName& rsOptionName : rptrArgument->GetOptionNames())
|
||||
{
|
||||
if (!ssArgument.empty())
|
||||
ssArgument += std::string(", ");
|
||||
if (rsOptionName.uiFlags & static_cast<uint32_t>(EArgumentFlags::option_argument))
|
||||
ssArgument += "-";
|
||||
else
|
||||
ssArgument += "--";
|
||||
ssArgument += rsOptionName.ssName;
|
||||
}
|
||||
|
||||
nStartOfTextBlock =
|
||||
std::max((ssArgument + rptrArgument->GetArgumentVar()->GetArgumentOptionMarkup()).size(), nStartOfTextBlock);
|
||||
};
|
||||
for (const std::shared_ptr<CArgumentDefBase>& rptrArgument : m_lstOptionArgs)
|
||||
{
|
||||
if (!rptrArgument->PartOfArgumentGroup(nArgumentGroup)) continue;
|
||||
fnLongestArgName(rptrArgument);
|
||||
}
|
||||
|
||||
// Add the space at the beginning and two spaces between argument name and the text
|
||||
nStartOfTextBlock += 3;
|
||||
|
||||
// Print the option arguments
|
||||
std::string ssGroup;
|
||||
auto fnPrintArg = [&](const std::shared_ptr<CArgumentDefBase>& rptrArgument) -> void
|
||||
{
|
||||
// Get the group information
|
||||
std::shared_ptr<SGroupDef> ptrGroup = rptrArgument->GetGroup();
|
||||
std::string ssTitle;
|
||||
if (ptrGroup) ssTitle = ptrGroup->ssTitle;
|
||||
if (ssTitle.empty()) ssTitle = "General options";
|
||||
std::string ssDescription;
|
||||
if (ptrGroup) ssDescription = ptrGroup->ssDescription;
|
||||
|
||||
// If there is a new group, print the information
|
||||
if (ssTitle != ssGroup)
|
||||
{
|
||||
rstream << std::endl;
|
||||
rstream << ssTitle << ":" << std::endl;
|
||||
ssGroup = ssTitle;
|
||||
if (!ssDescription.empty())
|
||||
rstream << ssDescription << std::endl;
|
||||
}
|
||||
|
||||
// Make a combined argument from argument parts
|
||||
std::string ssArgument;
|
||||
for (const CArgumentDefBase::SOptionName& rsOptionName : rptrArgument->GetOptionNames())
|
||||
{
|
||||
if (!ssArgument.empty())
|
||||
ssArgument += std::string(", ");
|
||||
if (rsOptionName.uiFlags & static_cast<uint32_t>(EArgumentFlags::option_argument))
|
||||
ssArgument += "-";
|
||||
else
|
||||
ssArgument += "--";
|
||||
ssArgument += rsOptionName.ssName;
|
||||
}
|
||||
|
||||
// The argument
|
||||
rstream << " ";
|
||||
rstream << ssArgument << rptrArgument->GetArgumentVar()->GetArgumentOptionMarkup() << " ";
|
||||
|
||||
// Calculate the starting point of the current help text (including dash and spaces)
|
||||
size_t nTextStart =
|
||||
std::max(nStartOfTextBlock, (ssArgument + rptrArgument->GetArgumentVar()->GetArgumentOptionMarkup()).size() + 3);
|
||||
|
||||
// Whitespace between the argument and the help text
|
||||
for (size_t n = 3 + (ssArgument + rptrArgument->GetArgumentVar()->GetArgumentOptionMarkup()).size();
|
||||
n < nTextStart; n++)
|
||||
rstream << " ";
|
||||
|
||||
// If the markup is different than the argument, add the markup to the helptext
|
||||
std::string ssHelpText = rptrArgument->GetHelpText() + "\n";
|
||||
|
||||
// Add the option details for this argument
|
||||
std::string ssOptionDetails =
|
||||
rptrArgument->GetArgumentVar()->GetArgumentOptionDetails(nPrintWidth - nStartOfTextBlock - 1);
|
||||
if (ssOptionDetails.size())
|
||||
{
|
||||
ssHelpText += ssOptionDetails;
|
||||
ssHelpText += "\n";
|
||||
}
|
||||
|
||||
// If there are examples, add the examples to the helptext
|
||||
if (rptrArgument->GetExamples().size())
|
||||
{
|
||||
ssHelpText += (rptrArgument->GetExamples().size() == 1 ? "Example:\n" : "Examples:\n");
|
||||
for (const std::string& rssExample : rptrArgument->GetExamples())
|
||||
{
|
||||
ssHelpText += rssExample + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Print the block of text
|
||||
PrintBlock(rstream, ssHelpText, nStartOfTextBlock, nTextStart, nPrintWidth - 1);
|
||||
};
|
||||
for (const std::shared_ptr<CArgumentDefBase>& rptrArgument : m_lstOptionArgs)
|
||||
{
|
||||
if (!rptrArgument->PartOfArgumentGroup(nArgumentGroup)) continue;
|
||||
fnPrintArg(rptrArgument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CCommandLine::PrintHelpText(std::ostream& rstream, const std::string& rssHelpText, size_t nPrintWidth /*= 0*/)
|
||||
{
|
||||
// Auto detection of the printable width?
|
||||
if (!nPrintWidth)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CONSOLE_SCREEN_BUFFER_INFO sScreenBufferInfo = {};
|
||||
if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &sScreenBufferInfo))
|
||||
nPrintWidth = static_cast<size_t>(sScreenBufferInfo.srWindow.Right) - static_cast<size_t>(sScreenBufferInfo.srWindow.Left) + 1;
|
||||
#else
|
||||
struct winsize ws;
|
||||
ioctl(0, TIOCGWINSZ, &ws);
|
||||
nPrintWidth = ws.ws_col;
|
||||
#endif
|
||||
if (nPrintWidth < 40)
|
||||
nPrintWidth = 80;
|
||||
}
|
||||
|
||||
// Print the help text if provided.
|
||||
size_t nPos = 0;
|
||||
size_t nLen = rssHelpText.empty() ? 0 : rssHelpText.size();
|
||||
size_t nScreenPos = 0;
|
||||
std::string ssSpace;
|
||||
std::string ssWord;
|
||||
std::string ssIndent;
|
||||
auto fnPrintWord = [&]()
|
||||
{
|
||||
// Something to do?
|
||||
if (!ssWord.size()) return;
|
||||
|
||||
// Indentation detection
|
||||
if (!nScreenPos)
|
||||
{
|
||||
if (ssSpace.size())
|
||||
ssIndent = ssSpace;
|
||||
else
|
||||
ssIndent.clear();
|
||||
}
|
||||
|
||||
// Newline needed?
|
||||
size_t nLastPos = nScreenPos + ssSpace.size() + ssWord.size();
|
||||
if (nLastPos >= nPrintWidth - 1)
|
||||
{
|
||||
rstream << std::endl << ssIndent;
|
||||
nScreenPos = ssIndent.size();
|
||||
ssSpace.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
rstream << ssSpace;
|
||||
nScreenPos += ssSpace.size();
|
||||
ssSpace.clear();
|
||||
}
|
||||
rstream << ssWord;
|
||||
nScreenPos += ssWord.size();
|
||||
ssWord.clear();
|
||||
};
|
||||
|
||||
// Build spaces and words and stream them
|
||||
while (nPos < nLen)
|
||||
{
|
||||
char c = rssHelpText[nPos++];
|
||||
if (std::strchr("\t\n\r\f\v ", c))
|
||||
{
|
||||
// Is there a word? Print the word...
|
||||
if (!ssWord.empty())
|
||||
fnPrintWord();
|
||||
|
||||
// Add the space....
|
||||
switch (c)
|
||||
{
|
||||
case '\n': // Newline
|
||||
rstream << std::endl;
|
||||
nScreenPos = 0;
|
||||
ssSpace.clear();
|
||||
break;
|
||||
case ' ': // Space
|
||||
ssSpace += c;
|
||||
break;
|
||||
case '\t': // Tab - convert to space
|
||||
ssSpace += std::string(4 - nScreenPos % 4, ' ');
|
||||
break;
|
||||
default: // Skip all other space characters
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect all characters for the word
|
||||
ssWord += c;
|
||||
}
|
||||
fnPrintWord();
|
||||
}
|
||||
|
||||
void CCommandLine::DumpArguments(std::ostream& rstream, bool bAll /*= true*/) const
|
||||
{
|
||||
rstream << "Dumping arguments:\n";
|
||||
|
||||
// Determine the length of the longest argument
|
||||
size_t nStartOfTextBlock = 9; // Minimum is 9
|
||||
auto fnLongestArgName = [&](const std::shared_ptr<CArgumentDefBase>& rptrArgument) -> void
|
||||
{
|
||||
// Make a combined argument from argument parts
|
||||
std::string ssArgument;
|
||||
for (const CArgumentDefBase::SOptionName& rsOptionName : rptrArgument->GetOptionNames())
|
||||
{
|
||||
if (!ssArgument.empty())
|
||||
ssArgument += std::string(", ");
|
||||
if (rsOptionName.uiFlags & static_cast<uint32_t>(EArgumentFlags::option_argument))
|
||||
ssArgument += "-";
|
||||
else
|
||||
ssArgument += "--";
|
||||
ssArgument += rsOptionName.ssName;
|
||||
}
|
||||
|
||||
nStartOfTextBlock = std::max(ssArgument.size(), nStartOfTextBlock);
|
||||
};
|
||||
for (const std::shared_ptr<CArgumentDefBase>& rptrArgument : m_lstOptionArgs)
|
||||
fnLongestArgName(rptrArgument);
|
||||
|
||||
// Add a double space (" ") at the beginning and the dash and space ("- ") between argument name and the value
|
||||
nStartOfTextBlock += 4;
|
||||
|
||||
// Maximum start block is 30
|
||||
nStartOfTextBlock = std::min(static_cast<size_t>(30), nStartOfTextBlock);
|
||||
|
||||
// Print header
|
||||
rstream << " Argument";
|
||||
for (size_t n = 10; n < nStartOfTextBlock; n++)
|
||||
rstream << " ";
|
||||
rstream << " | Set | Value\n";
|
||||
for (size_t n = 0; n < nStartOfTextBlock; n++)
|
||||
rstream << "-";
|
||||
rstream << "+-----+------------------------------\n";
|
||||
|
||||
// Print the arguments
|
||||
auto fnDumpArgs = [&](const std::shared_ptr<CArgumentDefBase>& rptrArgument) -> void
|
||||
{
|
||||
// Make a combined argument from argument parts
|
||||
std::string ssArgument;
|
||||
for (const CArgumentDefBase::SOptionName& rsOptionName : rptrArgument->GetOptionNames())
|
||||
{
|
||||
if (!ssArgument.empty())
|
||||
ssArgument += std::string(", ");
|
||||
if (rsOptionName.uiFlags & static_cast<uint32_t>(EArgumentFlags::option_argument))
|
||||
ssArgument += "-";
|
||||
else
|
||||
ssArgument += "--";
|
||||
ssArgument += rsOptionName.ssName;
|
||||
}
|
||||
|
||||
if (!bAll && !rptrArgument->GetArgumentVar()->IsArgumentAssigned()) return;
|
||||
|
||||
// The argument
|
||||
std::string ssArgumentName = ssArgument.empty() ? std::string("<default>") : (std::string("-") + ssArgument);
|
||||
rstream << " " << ssArgumentName;
|
||||
|
||||
// Calculate the starting point of the current help text
|
||||
size_t nTextStart = std::max(nStartOfTextBlock, ssArgumentName.size() + 3);
|
||||
|
||||
// Whitespace between the argument and the help text
|
||||
for (size_t n = 1 + ssArgumentName.size(); n < nTextStart; n++)
|
||||
rstream << " ";
|
||||
|
||||
// Print whether the argument was set or not
|
||||
rstream << (rptrArgument->GetArgumentVar()->IsArgumentAssigned() ? "| yes | " : "| no | ");
|
||||
|
||||
// The value
|
||||
rstream << rptrArgument->GetArgumentVar()->GetArgumentValueString() << std::endl;
|
||||
};
|
||||
if (m_ptrDefaultArg) fnDumpArgs(m_ptrDefaultArg);
|
||||
for (const std::shared_ptr<CArgumentDefBase>& rptrArgument : m_lstOptionArgs)
|
||||
fnDumpArgs(rptrArgument);
|
||||
}
|
||||
|
||||
std::vector<std::string> CCommandLine::IncompatibleArguments(size_t nArgumentGroup, bool bFull /*= true*/) const
|
||||
{
|
||||
std::vector<std::string> vecIncompatible;
|
||||
for (auto& prArgument : m_lstSupplied)
|
||||
{
|
||||
// Only valid for options, not for default arguments
|
||||
if (prArgument.first.get().CheckFlag(EArgumentFlags::default_argument)) continue;
|
||||
|
||||
// Is the argument compatible?
|
||||
if (prArgument.first.get().PartOfArgumentGroup(nArgumentGroup)) continue;
|
||||
|
||||
// Full option requested?
|
||||
if (bFull)
|
||||
{
|
||||
vecIncompatible.push_back(prArgument.second);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make lower case string
|
||||
auto fnMakeLower = [](const std::string& rss)
|
||||
{
|
||||
std::string rssTemp;
|
||||
rssTemp.reserve(rss.size());
|
||||
for (char c : rss)
|
||||
rssTemp += static_cast<char>(std::tolower(c));
|
||||
return rssTemp;
|
||||
};
|
||||
|
||||
// Find the option text
|
||||
std::string ssOptionText;
|
||||
std::string ssArgumentLC = fnMakeLower(prArgument.second);
|
||||
for (auto& rsOptionText : prArgument.first.get().GetOptionNames())
|
||||
{
|
||||
std::string ssOptionNameLC = fnMakeLower(rsOptionText.ssName);
|
||||
std::string ssOptionLC = std::string("-") + ssOptionNameLC;
|
||||
if (rsOptionText.uiFlags & static_cast<uint32_t>(EArgumentFlags::option_argument) &&
|
||||
ssArgumentLC.substr(0, ssOptionLC.size()) == ssOptionLC)
|
||||
{
|
||||
ssOptionText = prArgument.second.substr(0, ssOptionLC.size());
|
||||
break;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
ssOptionLC = std::string("/") + ssOptionNameLC;
|
||||
if (rsOptionText.uiFlags & static_cast<uint32_t>(EArgumentFlags::option_argument) &&
|
||||
ssArgumentLC.substr(0, ssOptionLC.size()) == ssOptionLC)
|
||||
{
|
||||
ssOptionText = prArgument.second.substr(0, ssOptionLC.size());
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
ssOptionLC = std::string("--") + ssOptionNameLC;
|
||||
if (rsOptionText.uiFlags & static_cast<uint32_t>(EArgumentFlags::sub_option_argument) &&
|
||||
ssArgumentLC.substr(0, ssOptionLC.size()) == ssOptionLC)
|
||||
{
|
||||
ssOptionText = prArgument.second.substr(0, ssOptionLC.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Just in case... should have been
|
||||
if (ssOptionText.empty())
|
||||
ssOptionText = prArgument.second;
|
||||
|
||||
// Store the options
|
||||
vecIncompatible.push_back(ssOptionText);
|
||||
}
|
||||
|
||||
return vecIncompatible;
|
||||
}
|
||||
|
||||
void PrintBlock(std::ostream& rstream, const std::string& rssText, size_t nIndentPos, size_t nCurrentPos, size_t nMaxPos)
|
||||
{
|
||||
// Scan through the help text and add the text word for word, adding \n if necessary
|
||||
size_t nScanPos = 0, nPrintPos = 0;
|
||||
do
|
||||
{
|
||||
// Determine whether the next text is white space
|
||||
bool bDoProcess = false;
|
||||
bool bEOL = false;
|
||||
switch (rssText[nScanPos])
|
||||
{
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
bDoProcess = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Special case, the last character
|
||||
if (nScanPos == rssText.size() - 1)
|
||||
{
|
||||
if (!bDoProcess) // Include when this is not whitespace
|
||||
nScanPos++;
|
||||
|
||||
bEOL = true;
|
||||
bDoProcess = true;
|
||||
}
|
||||
|
||||
// When not whitespace, check the next scan
|
||||
if (!bDoProcess)
|
||||
{
|
||||
nScanPos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check whether the text still fits (or if not, whether we are not exactly at
|
||||
// the beginning of a new text block
|
||||
if (nCurrentPos + (nScanPos - nPrintPos) > nMaxPos &&
|
||||
nCurrentPos != nIndentPos)
|
||||
{
|
||||
// Insert a newline
|
||||
rstream << std::endl;
|
||||
|
||||
// And fill up whitespace
|
||||
for (size_t n = 0; n < nIndentPos; n++)
|
||||
rstream << " ";
|
||||
|
||||
nCurrentPos = nIndentPos;
|
||||
}
|
||||
|
||||
// Print the text until the end of the line or the scan pos
|
||||
size_t nTextLen = std::min(nScanPos - nPrintPos, nMaxPos - nCurrentPos);
|
||||
rstream << rssText.substr(nPrintPos, nTextLen);
|
||||
nPrintPos += nTextLen;
|
||||
nScanPos = nPrintPos;
|
||||
nCurrentPos += nTextLen;
|
||||
if (nScanPos >= rssText.size()) continue;
|
||||
|
||||
// Print the whitespace at nScanPos
|
||||
char chTemp = rssText[nScanPos]; // Needed since the scan position changes
|
||||
switch (chTemp)
|
||||
{
|
||||
case ' ':
|
||||
// Increase the position
|
||||
nCurrentPos++;
|
||||
|
||||
// Beyond the end of the line?
|
||||
if (nCurrentPos < nMaxPos)
|
||||
rstream << " ";
|
||||
break;
|
||||
case '\t':
|
||||
// Beyond the end of the line?
|
||||
if (nCurrentPos + (8 - nCurrentPos % 8) > nMaxPos)
|
||||
nCurrentPos = nMaxPos + 1;
|
||||
nPrintPos++;
|
||||
nScanPos++;
|
||||
break;
|
||||
case '\n':
|
||||
rstream << std::endl;
|
||||
nPrintPos++;
|
||||
nScanPos++;
|
||||
// Do not indent any more with the last character
|
||||
for (size_t n = 0; !bEOL && n < nIndentPos; n++)
|
||||
rstream << " ";
|
||||
nCurrentPos = nIndentPos;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Newline needed?
|
||||
if (nCurrentPos > nMaxPos)
|
||||
{
|
||||
rstream << std::endl;
|
||||
// Do not indent any more with the last character
|
||||
for (size_t n = 0; !bEOL && n < nIndentPos; n++)
|
||||
rstream << " ";
|
||||
nCurrentPos = nIndentPos;
|
||||
}
|
||||
// Print the tab and increase the scan position
|
||||
switch (chTemp)
|
||||
{
|
||||
case '\t':
|
||||
nCurrentPos += 8 - nCurrentPos % 8;
|
||||
rstream << "\t";
|
||||
break;
|
||||
case '\n':
|
||||
break;
|
||||
case ' ':
|
||||
nPrintPos++;
|
||||
nScanPos++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Hypothetical comparison; is always true. This is wanted. Suppress CppCheck warning
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
} while (nScanPos < rssText.size());
|
||||
}
|
||||
2778
global/cmdlnparser/cmdlnparser.h
Normal file
2778
global/cmdlnparser/cmdlnparser.h
Normal file
File diff suppressed because it is too large
Load Diff
2362
global/dbcparser/dbcparser.cpp
Normal file
2362
global/dbcparser/dbcparser.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1190
global/dbcparser/dbcparser.h
Normal file
1190
global/dbcparser/dbcparser.h
Normal file
File diff suppressed because it is too large
Load Diff
207
global/debug_log.h
Normal file
207
global/debug_log.h
Normal file
@@ -0,0 +1,207 @@
|
||||
#ifndef DEBUG_LOG_H
|
||||
#define DEBUG_LOG_H
|
||||
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include "exec_dir_helper.h"
|
||||
|
||||
/**
|
||||
* @brief Enable debug log by defining it and assigning it the value 1.
|
||||
*/
|
||||
#define ENABLE_DEBUG_LOG 1
|
||||
|
||||
/**
|
||||
* @brief Debug namespace
|
||||
*/
|
||||
namespace debug
|
||||
{
|
||||
/**
|
||||
* @brief Logger class, creating a log file at the location of the executable and using the same filename as the executable
|
||||
* with the .log extension.
|
||||
*/
|
||||
class CLogger
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor, creating the file. If the file exists, it will be overwritten.
|
||||
*/
|
||||
CLogger()
|
||||
{
|
||||
m_pathLogFile = GetExecDirectory() / GetExecFilename().replace_extension(".log");
|
||||
std::unique_lock<std::mutex> lock(m_mtxLogger);
|
||||
std::ofstream fstream(m_pathLogFile, std::ios_base::out | std::ios_base::trunc);
|
||||
if (fstream.is_open())
|
||||
{
|
||||
fstream << "Starting log of " << GetExecFilename().generic_u8string() << std::endl;
|
||||
fstream.close();
|
||||
}
|
||||
std::clog << "Starting log of " << GetExecFilename().generic_u8string() << std::flush << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CLogger()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxLogger);
|
||||
std::ofstream fstream(m_pathLogFile, std::ios_base::out | std::ios_base::app);
|
||||
if (fstream.is_open())
|
||||
{
|
||||
fstream << "End of log..." << std::endl;
|
||||
fstream.close();
|
||||
}
|
||||
std::clog << "End of log..." << std::flush << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Log an entry. The text will be indented dependable on the depth of the logging.
|
||||
* @param[in] rss Reference to the string to log.
|
||||
*/
|
||||
void Log(const std::string& rss)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxLogger);
|
||||
std::ofstream fstream(m_pathLogFile, std::ios_base::out | std::ios_base::app);
|
||||
std::string ssIndent(m_nDepth, '>');
|
||||
if (!ssIndent.empty()) ssIndent += ' ';
|
||||
if (fstream.is_open())
|
||||
{
|
||||
fstream << ssIndent << rss << std::endl;
|
||||
fstream.close();
|
||||
}
|
||||
std::clog << ssIndent << rss << std::flush << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Increase the depth. This will indent the log.
|
||||
*/
|
||||
void IncrDepth()
|
||||
{
|
||||
m_nDepth++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decrease the depth. This might decrease the indentation.
|
||||
*/
|
||||
void DecrDepth()
|
||||
{
|
||||
if (m_nDepth)
|
||||
m_nDepth--;
|
||||
}
|
||||
private:
|
||||
std::filesystem::path m_pathLogFile; ///< Path to the log file.
|
||||
std::mutex m_mtxLogger; ///< Protect against multiple log entries at the same time.
|
||||
size_t m_nDepth; ///< Depth level for indentation.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Global logger function. A call to this function will cause an instance to be available.
|
||||
* @return Reference to the logger.
|
||||
*/
|
||||
inline CLogger& GetLogger()
|
||||
{
|
||||
static CLogger logger;
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function logger class logging the entering and leaving of the function if places at the beginning of the function.
|
||||
*/
|
||||
class CFuncLogger
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor creating a log entry for entering the function
|
||||
* @param[in] rssFunc Reference to the function name.
|
||||
* @param[in] rssFile Reference to the source file the function is implemented in.
|
||||
* @param[in] nLine Reference to the line the function logger object is created.
|
||||
*/
|
||||
CFuncLogger(const std::string& rssFunc, const std::string& rssFile, size_t nLine) : m_ssFunc(rssFunc)
|
||||
{
|
||||
GetLogger().Log(std::string("Enter function:") + rssFunc + " - file " + rssFile + " - line " + std::to_string(nLine));
|
||||
GetLogger().IncrDepth();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor creating a log entry for leaving the function.
|
||||
*/
|
||||
~CFuncLogger()
|
||||
{
|
||||
GetLogger().DecrDepth();
|
||||
GetLogger().Log(std::string("Leave function:") + m_ssFunc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Log a function checkpoint.
|
||||
* @param[in] nLine The line number of this checkpoint.
|
||||
*/
|
||||
void Checkpoint(size_t nLine)
|
||||
{
|
||||
GetLogger().Log(std::string("Checkpoint #") + std::to_string(m_nCounter++) + " - line " + std::to_string(nLine));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Log a string.
|
||||
* @param[in] rss Reference to the string to log.
|
||||
*/
|
||||
void Log(const std::string& rss) const
|
||||
{
|
||||
GetLogger().Log(rss);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_ssFunc; ///< Name of the function.
|
||||
size_t m_nCounter = 1; ///< Checkpoint counter.
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
|
||||
#if ENABLE_DEBUG_LOG == 1
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/**
|
||||
* @brief Macro to create a function logger object using the function name, the file name and the line number from the compiler.
|
||||
*/
|
||||
#define FUNC_LOGGER() debug::CFuncLogger logger(__FUNCSIG__, __FILE__, __LINE__)
|
||||
#else
|
||||
/**
|
||||
* @brief Macro to create a function logger object using the function name, the file name and the line number from the compiler.
|
||||
*/
|
||||
#define FUNC_LOGGER() debug::CFuncLogger logger(__PRETTY_FUNCTION__, __FILE__, __LINE__)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Macro to set a checkpoint providing the line number from the compiler.
|
||||
*/
|
||||
#define CHECKPOINT() logger.Checkpoint(__LINE__)
|
||||
|
||||
/**
|
||||
* @brief Log a message
|
||||
*/
|
||||
#define FUNC_LOG(msg) logger.Log(msg)
|
||||
|
||||
#else
|
||||
/**
|
||||
* @brief Macro to create a function logger object using the function name, the file name and the line number from the compiler
|
||||
* (disabled).
|
||||
*/
|
||||
#define FUNC_LOGGER()
|
||||
|
||||
/**
|
||||
* @brief Macro to set a checkpoint providing the line number from the compiler (disabled).
|
||||
*/
|
||||
#define CHECKPOINT()
|
||||
|
||||
/**
|
||||
* @brief Log a message (disabled)
|
||||
*/
|
||||
#define FUNC_LOG(msg)
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#endif // !defined DEBUG_LOG_H
|
||||
96
global/exec_dir_helper.h
Normal file
96
global/exec_dir_helper.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
*
|
||||
* @file exec_dir_helper.h
|
||||
* @brief This file contains helper functions e.g. filesystem
|
||||
* @version 0.1
|
||||
* @date 2022.11.14
|
||||
* @author Thomas.pfleiderer@zf.com
|
||||
* @copyright Copyright ZF Friedrichshaven AG (c) 2022
|
||||
*
|
||||
*/
|
||||
#ifndef EXEC_DIR_HELPER_H
|
||||
#define EXEC_DIR_HELPER_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
#pragma push_macro("GetObject")
|
||||
#undef GetObject
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <array>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("GetObject")
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
#elif defined __linux__
|
||||
#include <linux/limits.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#error OS is not supported
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Get the absolute path to the currently run executable
|
||||
* @return Absolute std::filesystem::path to the currently run executable
|
||||
*/
|
||||
inline std::filesystem::path GetExecDirectory()
|
||||
{
|
||||
static std::filesystem::path pathExeDir;
|
||||
if (!pathExeDir.empty()) return pathExeDir;
|
||||
#ifdef _WIN32
|
||||
// Windows specific
|
||||
std::wstring ssPath(32768, '\0');
|
||||
GetModuleFileNameW(NULL, ssPath.data(), static_cast<DWORD>(ssPath.size() - 1));
|
||||
#elif defined __linux__
|
||||
// Linux specific
|
||||
std::string ssPath(PATH_MAX + 1, '\0');
|
||||
const ssize_t nCount = readlink("/proc/self/exe", ssPath.data(), PATH_MAX);
|
||||
if (nCount < 0 || nCount >= PATH_MAX)
|
||||
return pathExeDir; // some error
|
||||
ssPath.at(nCount) = '\0';
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
pathExeDir = std::filesystem::path{ssPath.c_str()}.parent_path() / ""; // To finish the folder path with (back)slash
|
||||
return pathExeDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the filename of the currently run executable
|
||||
* @return The filename to the currently run executable
|
||||
*/
|
||||
inline std::filesystem::path GetExecFilename()
|
||||
{
|
||||
static std::filesystem::path pathExeFilename;
|
||||
if (!pathExeFilename.empty()) return pathExeFilename;
|
||||
#ifdef _WIN32
|
||||
// Windows specific
|
||||
std::wstring ssPath(32768, '\0');
|
||||
GetModuleFileNameW(NULL, ssPath.data(), static_cast<DWORD>(ssPath.size() - 1));
|
||||
#elif defined __linux__
|
||||
// Linux specific
|
||||
std::string ssPath(PATH_MAX + 1, '\0');
|
||||
const ssize_t nCount = readlink("/proc/self/exe", ssPath.data(), PATH_MAX);
|
||||
if (nCount < 0 || nCount >= PATH_MAX)
|
||||
return pathExeFilename; // some error
|
||||
ssPath.at(nCount) = '\0';
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
pathExeFilename = std::filesystem::path{ssPath.c_str()}.filename();
|
||||
return pathExeFilename;
|
||||
}
|
||||
|
||||
#endif // !defined EXEC_DIR_HELPER_H
|
||||
143
global/filesystem_helper.h
Normal file
143
global/filesystem_helper.h
Normal file
@@ -0,0 +1,143 @@
|
||||
#ifndef FILESYSTEM_HELPER
|
||||
#define FILESYSTEM_HELPER
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <regex>
|
||||
|
||||
/**
|
||||
* @brief Check for a match of the path with the pattern containing zero or more wildcards.
|
||||
* @param[in] rpath Reference to the path to compare.
|
||||
* @param[in] rpathPattern Reference to the path containing zero or more wildcards.
|
||||
* @return Returns 'true' when the paths match, 'false' when not.
|
||||
*/
|
||||
inline bool MatchFileWithWildcards(const std::filesystem::path& rpath, const std::filesystem::path& rpathPattern)
|
||||
{
|
||||
std::regex regexStarReplace("\\*");
|
||||
std::regex regexQuestionmarkReplace("\\?");
|
||||
|
||||
std::string ssWildcardPattern = std::regex_replace(
|
||||
std::regex_replace(rpathPattern.generic_u8string(), regexStarReplace, ".*"),
|
||||
regexQuestionmarkReplace, ".");
|
||||
|
||||
std::regex regexWildcardPattern("^" + ssWildcardPattern + "$");
|
||||
|
||||
return std::regex_match(rpath.generic_u8string(), regexWildcardPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find the files using a split-up vector containing the path chunks and potential wildcard patterns.
|
||||
* @param[in] rvecFiles Reference to the list of files being found. This list will be extended iteratively.
|
||||
* @param[in] rpathBase Reference to the base path to match with the path/pattern chunks from the vector starting at the specified
|
||||
* index.
|
||||
* @param[in] rvecPatternChunks Reference with the split search path containing the path/pattern chunks.
|
||||
* @param[in] nIndex The current index that must match with the sub-directories and files in the base path.
|
||||
* @param[in] rsetSearch Set of search paths being updated to prevent searching a path multiple times. Searching multiple times
|
||||
* cannot be excluded when using generic wildcards that could represent zero or more sub-directories "*".
|
||||
*/
|
||||
void FindFilesWithWildcards(std::vector<std::filesystem::path>& rvecFiles, const std::filesystem::path& rpathBase,
|
||||
const std::vector<std::string>& rvecPatternChunks, size_t nIndex, std::set<std::filesystem::path>& rsetSearch)
|
||||
{
|
||||
// Done if itPos points to the end of the split vector
|
||||
if (nIndex >= rvecPatternChunks.size())
|
||||
{
|
||||
// If this is a file, add the file to the list
|
||||
if (std::filesystem::is_regular_file(rpathBase))
|
||||
rvecFiles.push_back(rpathBase);
|
||||
else if (std::filesystem::is_directory(rpathBase))
|
||||
{
|
||||
// Recursively iterate through the directories and add all files.
|
||||
for (auto const& dir_entry : std::filesystem::recursive_directory_iterator{ rpathBase })
|
||||
{
|
||||
if (std::filesystem::is_regular_file(dir_entry.path()))
|
||||
rvecFiles.push_back(dir_entry.path());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& rssPatternChunk = rvecPatternChunks[nIndex];
|
||||
|
||||
// Further processing is only possible if the pathBase is a directory...
|
||||
if (!std::filesystem::is_directory(rpathBase))
|
||||
{
|
||||
// In some rare cases, the path cannot be identified as directory, but still is one. Add the next chunk.
|
||||
FindFilesWithWildcards(rvecFiles, rpathBase / rssPatternChunk, rvecPatternChunks, nIndex + 1, rsetSearch);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether the current position contains wildcards; if not, add it to the base path and go one deeper.
|
||||
if (rvecPatternChunks[nIndex].find_first_of("*?") == std::string::npos)
|
||||
{
|
||||
FindFilesWithWildcards(rvecFiles, rpathBase / rssPatternChunk, rvecPatternChunks, nIndex + 1, rsetSearch);
|
||||
return;
|
||||
}
|
||||
|
||||
// Due to the use of wildcards, it could happen, that directories get searched multiple times. If searched before, skip another
|
||||
// search...
|
||||
std::filesystem::path pathSearchPath = rpathBase / rssPatternChunk;
|
||||
if (rsetSearch.find(pathSearchPath) != rsetSearch.end()) return;
|
||||
rsetSearch.insert(pathSearchPath);
|
||||
|
||||
// Iterator through the base directory to see if a matching path can be made
|
||||
std::filesystem::path pathPattern = rpathBase;
|
||||
pathPattern /= rssPatternChunk;
|
||||
for (auto const& dir_entry : std::filesystem::directory_iterator{ rpathBase })
|
||||
{
|
||||
// Does the pattern fit?
|
||||
if (MatchFileWithWildcards(dir_entry.path(), pathPattern))
|
||||
{
|
||||
// Special case: if the current string is "*" this might also be valid for any child path. Check this first...
|
||||
if (rssPatternChunk == "*")
|
||||
FindFilesWithWildcards(rvecFiles, dir_entry.path(), rvecPatternChunks, nIndex, rsetSearch);
|
||||
|
||||
// Increase the position of our pattern, check whether the path first
|
||||
FindFilesWithWildcards(rvecFiles, dir_entry.path(), rvecPatternChunks, nIndex + 1, rsetSearch);
|
||||
|
||||
// Special case: if the current string is "*" the rest of the pattern might fit the current path (doing as if the
|
||||
// wildcard doesn't exist).
|
||||
if (rssPatternChunk == "*")
|
||||
FindFilesWithWildcards(rvecFiles, rpathBase, rvecPatternChunks, nIndex + 1, rsetSearch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Find the files using the provided path.
|
||||
/// @details Search for the files matching the providing search path. The search path could contain wildcards ('*' and '?').
|
||||
/// Examples of path search criteria:
|
||||
/// @code
|
||||
/// /usr/* - all files in /usr and all sub-directories
|
||||
/// /usr/*.sdv - all .sdv-files in /usr
|
||||
/// /usr/*/*.sdv - all .sdv-files in /usr and all sub-directories
|
||||
/// /usr/*/abc/*.sdv - looks in /usr for all .sdv-files in a sub-directory abc
|
||||
/// /usr/abc*/*.sdv - looks in /usr for all directories starting with abc and takes the .sdv-files from this directories
|
||||
/// @endcode
|
||||
/// @param[in] rpathSearch Search path with zero or more wildcards. The path is assumed to be an absolute path.
|
||||
/// @return Returns a vector with all the found files (full path).
|
||||
///
|
||||
inline std::vector<std::filesystem::path> FindFilesWithWildcards(const std::filesystem::path& rpathSearch)
|
||||
{
|
||||
// NOTE: The std::filesystem::path type has an iterator to iterate through the path. Unfortunately it is not possible to clone
|
||||
// an iterator and iterate independently further... the clone operators on the same iterator as the original.
|
||||
// The solution is to build a vector with the path-chunks and a vector index, which in any case is cloneable.
|
||||
|
||||
// Split the path
|
||||
std::vector<std::filesystem::path> vecFiles;
|
||||
std::vector<std::string> vecPatternChunks;
|
||||
for (const std::filesystem::path& rpathChunk : rpathSearch)
|
||||
vecPatternChunks.push_back(rpathChunk.u8string());
|
||||
if (vecPatternChunks.empty()) return vecFiles;
|
||||
|
||||
// Build the root search path without the wildcard
|
||||
std::set<std::filesystem::path> setSearch;
|
||||
FindFilesWithWildcards(vecFiles, vecPatternChunks[0], vecPatternChunks, 1, setSearch);
|
||||
return vecFiles;
|
||||
}
|
||||
|
||||
|
||||
#endif // !defined FILESYSTEM_HELPER
|
||||
177
global/flags.h
Normal file
177
global/flags.h
Normal file
@@ -0,0 +1,177 @@
|
||||
#ifndef FLAGS_HELPER_H
|
||||
#define FLAGS_HELPER_H
|
||||
|
||||
#include <type_traits>
|
||||
#include <initializer_list>
|
||||
|
||||
/**
|
||||
* @brief Helper namespace
|
||||
*/
|
||||
namespace hlpr
|
||||
{
|
||||
/**
|
||||
* @brief Generic flags type class based on flags defined in an enum.
|
||||
* @tparam TEnum The enum type that is used to define flags.
|
||||
*/
|
||||
template <typename TEnum>
|
||||
struct flags
|
||||
{
|
||||
// Only works with enums.
|
||||
static_assert(std::is_enum_v<TEnum>);
|
||||
|
||||
/// The enum base type.
|
||||
using enum_type = std::underlying_type_t<TEnum>;
|
||||
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
flags() = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor
|
||||
* @param[in] rflags Reference to the flags to copy from.
|
||||
*/
|
||||
flags(const flags& rflags) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
* @param[in] rflags Reference to the flags to move from.
|
||||
*/
|
||||
flags(flags&& rflags) : _tValue(rflags._tValue) { rflags._tValue = 0; }
|
||||
|
||||
/**
|
||||
* @brief Constructor with flags value assignment.
|
||||
* @param[in] tValue The flags value to assign.
|
||||
*/
|
||||
explicit flags(enum_type tValue) : _tValue(tValue) {}
|
||||
|
||||
/**
|
||||
* @brief Constructor with single value assignment.
|
||||
* @param[in] eValue The single value to assign.
|
||||
*/
|
||||
flags(TEnum eValue) : _tValue(static_cast<enum_type>(eValue)) {}
|
||||
|
||||
/**
|
||||
* @brief Constructor with initializer list
|
||||
* @param[in] init The initializer list.
|
||||
*/
|
||||
flags(std::initializer_list<TEnum> init)
|
||||
{
|
||||
for (TEnum eValue : init)
|
||||
_tValue |= static_cast<enum_type>(eValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Flags class assignment.
|
||||
* @param[in] rflags Reference to the flags to copy from.
|
||||
* @return Returns a reference to this class.
|
||||
*/
|
||||
flags& operator=(const flags& rflags) = default;
|
||||
|
||||
/**
|
||||
* @brief Flags move assignment.
|
||||
* @param[in] rflags Reference to the flags to move from.
|
||||
* @return Returns a reference to this class.
|
||||
*/
|
||||
flags& operator=(flags&& rflags) { _tValue = rflags._tValue; rflags._tValue = 0; return *this; }
|
||||
|
||||
/**
|
||||
* @brief Flags value assignment.
|
||||
* @param[in] tValue The flags value to assign.
|
||||
* @return Returns a reference to this class.
|
||||
*/
|
||||
flags& operator=(enum_type tValue) { _tValue = tValue; return *this; }
|
||||
|
||||
/**
|
||||
* @brief Single value assignment.
|
||||
* @param[in] eValue The single value to assign.
|
||||
* @return Returns a reference to this class.
|
||||
*/
|
||||
flags& operator=(TEnum eValue) { _tValue = static_cast<enum_type>(eValue); return *this; }
|
||||
|
||||
/**
|
||||
* @brief Cast operator
|
||||
* @return The flags value.
|
||||
*/
|
||||
operator enum_type() const { return _tValue; }
|
||||
|
||||
/**
|
||||
* @brief Add a flag.
|
||||
* @param[in] eValue The flag value to set.
|
||||
* @return Reference to this class.
|
||||
*/
|
||||
flags& operator+=(TEnum eValue) { _tValue |= static_cast<enum_type>(eValue); return *this; }
|
||||
|
||||
/**
|
||||
* @brief Remove a flag.
|
||||
* @param[in] eValue The flag value to remove.
|
||||
* @return Reference to this class.
|
||||
*/
|
||||
flags& operator-=(TEnum eValue) { _tValue &= ~static_cast<enum_type>(eValue); return *this; }
|
||||
|
||||
/**
|
||||
* @brief Add a flag to a value copy.
|
||||
* @param[in] eValue The flag value to set.
|
||||
* @return The result flags.
|
||||
*/
|
||||
flags operator+(TEnum eValue) { flags flagsCopy(_tValue); flagsCopy.add(eValue); return flagsCopy; }
|
||||
|
||||
/**
|
||||
* @brief Remove a flag from a value copy.
|
||||
* @param[in] eValue The flag value to remove.
|
||||
* @return The result flags.
|
||||
*/
|
||||
flags operator-(TEnum eValue) { flags flagsCopy(_tValue); flagsCopy.remove(eValue); return flagsCopy; }
|
||||
|
||||
/**
|
||||
* @brief Check the flags for the availability of the flag.
|
||||
* @param[in] eValue The flag to check for.
|
||||
* @return Returns whether the flag was set or not.
|
||||
*/
|
||||
bool operator&(TEnum eValue) const { return (_tValue & static_cast<enum_type>(eValue)) ? true : false; }
|
||||
|
||||
/**
|
||||
* @brief Return the flags value.
|
||||
* @return The flags value.
|
||||
*/
|
||||
enum_type get() const { return _tValue; }
|
||||
|
||||
/**
|
||||
* @brief Add a flag.
|
||||
* @param[in] eValue The flag value to set.
|
||||
*/
|
||||
void add(TEnum eValue) { _tValue |= static_cast<enum_type>(eValue); }
|
||||
|
||||
/**
|
||||
* @brief Remove a flag.
|
||||
* @param[in] eValue The flag value to remove.
|
||||
*/
|
||||
void remove(TEnum eValue) { _tValue &= ~static_cast<enum_type>(eValue); }
|
||||
|
||||
/**
|
||||
* @brief Check the flags for the availability of the flag.
|
||||
* @param[in] eValue The flag to check for.
|
||||
* @return Returns whether the flag was set or not.
|
||||
*/
|
||||
bool check(TEnum eValue) const { return _tValue & static_cast<enum_type>(eValue); }
|
||||
|
||||
/**
|
||||
* @brief Check the flags for the availability of at least one flag.
|
||||
* @param[in] flagsValue The flags to use for the check.
|
||||
* @return Returns whether at least one flag was set.
|
||||
*/
|
||||
bool check_any(flags flagsValue) const { return _tValue & flagsValue._tValue; }
|
||||
|
||||
/**
|
||||
* @brief Check the flags for the availability of all supplied flags.
|
||||
* @remarks Check for the supplied flags. Additional flags might also be set, but have no influence on the outcome.
|
||||
* @param[in] flagsValue The flags to use for the check.
|
||||
* @return Returns whether all supplied flags were set.
|
||||
*/
|
||||
bool check_all(flags flagsValue) const { return (_tValue & flagsValue._tValue) == flagsValue._tValue; }
|
||||
|
||||
enum_type _tValue = 0; ///< The flag value.
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !defined FLAGS_HELPER_H
|
||||
252
global/ipc_named_mutex.h
Normal file
252
global/ipc_named_mutex.h
Normal file
@@ -0,0 +1,252 @@
|
||||
#ifndef IPC_NAMED_MUTEX_H
|
||||
#define IPC_NAMED_MUTEX_H
|
||||
|
||||
#ifdef _WIN32
|
||||
// Resolve conflict
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
//#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
#elif defined __unix__
|
||||
#include <fcntl.h> /* For O_* constants */
|
||||
#include <sys/stat.h> /* For mode constants */
|
||||
#include <semaphore.h>
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
|
||||
/**
|
||||
* @brief Inter-process-communication namespace
|
||||
*/
|
||||
namespace ipc
|
||||
{
|
||||
/**
|
||||
* @brief Named mutex allowing interprocess synchronization. This mutex class can be used with the standard C++ library.
|
||||
*/
|
||||
class named_mutex
|
||||
{
|
||||
public:
|
||||
#ifdef _WIN32
|
||||
/// Native handle type.
|
||||
using native_handle_type = HANDLE;
|
||||
#elif defined __unix__
|
||||
/// Native handle type.
|
||||
using native_handle_type = sem_t*;
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Named mutex constructor. Opens or creates a named mutex. This mutex is unlocked.
|
||||
* @param[in] szName Pointer to the zero terminated name of the mutex. Can be null to automatically generate a name.
|
||||
*/
|
||||
named_mutex(const char* szName = nullptr) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor is deleted.
|
||||
*/
|
||||
named_mutex(const named_mutex&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param[in] rmtx Reference to the mutex to move from.
|
||||
*/
|
||||
named_mutex(named_mutex&& rmtx) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~named_mutex();
|
||||
|
||||
/**
|
||||
* @brief Assignment operator is deleted.
|
||||
* @return Reference to this class.
|
||||
*/
|
||||
named_mutex& operator=(const named_mutex&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move operator.
|
||||
* @param[in] rmtx Reference to the mutex to move from.
|
||||
* @return Reference to this class.
|
||||
*/
|
||||
named_mutex& operator=(named_mutex&& rmtx) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Locks the mutex, blocks if the mutex is not available.
|
||||
*/
|
||||
void lock();
|
||||
|
||||
/**
|
||||
* @brief Tries to lock the mutex, returns if the mutex is not available.
|
||||
* @return Returns 'true' when the lock was successful; 'false' when not.
|
||||
*/
|
||||
bool try_lock();
|
||||
|
||||
/**
|
||||
* @brief Unlocks the mutex.
|
||||
*/
|
||||
void unlock();
|
||||
|
||||
/**
|
||||
* @brief Return the mutex name.
|
||||
* @return Reference to the name of the mutex.
|
||||
*/
|
||||
const std::string& name() const;
|
||||
|
||||
/**
|
||||
* @brief Return the underlying implementation-defined native handle object.
|
||||
* @return Handle to the mutex.
|
||||
*/
|
||||
native_handle_type native_handle();
|
||||
|
||||
private:
|
||||
std::string m_ssName; ///< Name of the mutex.
|
||||
native_handle_type m_handle; ///< Mutex handle.
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
inline named_mutex::named_mutex(const char* szName) noexcept : m_handle(nullptr)
|
||||
{
|
||||
if (szName)
|
||||
m_ssName = szName;
|
||||
else
|
||||
{
|
||||
std::srand(static_cast<unsigned>(std::time(nullptr))); // Use current time as seed for random generator
|
||||
m_ssName = std::string("NAMED_MUTEX_") + std::to_string(std::rand());
|
||||
}
|
||||
m_handle = CreateMutexA(nullptr, FALSE, (std::string("Global\\") + m_ssName).c_str());
|
||||
}
|
||||
|
||||
inline named_mutex::named_mutex(named_mutex&& rmtx) noexcept : m_handle(rmtx.m_handle)
|
||||
{
|
||||
rmtx.m_handle = nullptr;
|
||||
}
|
||||
|
||||
inline named_mutex::~named_mutex()
|
||||
{
|
||||
if (m_handle && m_handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(m_handle);
|
||||
}
|
||||
|
||||
// False positive warning of CppCheck: m_handle is not assigned a value. This is not the case. Suppress the warning.
|
||||
// cppcheck-suppress operatorEqVarError
|
||||
inline named_mutex& named_mutex::operator=(named_mutex&& rmtx) noexcept
|
||||
{
|
||||
if (m_handle && m_handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(m_handle);
|
||||
m_handle = rmtx.m_handle;
|
||||
rmtx.m_handle = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline void named_mutex::lock()
|
||||
{
|
||||
if (m_handle && m_handle != INVALID_HANDLE_VALUE)
|
||||
WaitForSingleObject(m_handle, INFINITE);
|
||||
}
|
||||
|
||||
inline bool named_mutex::try_lock()
|
||||
{
|
||||
if (m_handle && m_handle != INVALID_HANDLE_VALUE)
|
||||
return WaitForSingleObject(m_handle, 0) == WAIT_OBJECT_0;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void named_mutex::unlock()
|
||||
{
|
||||
if (m_handle && m_handle != INVALID_HANDLE_VALUE)
|
||||
ReleaseMutex(m_handle);
|
||||
}
|
||||
|
||||
inline const std::string& named_mutex::name() const
|
||||
{
|
||||
return m_ssName;
|
||||
}
|
||||
|
||||
inline named_mutex::native_handle_type named_mutex::native_handle()
|
||||
{
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
#elif defined __unix__
|
||||
|
||||
inline named_mutex::named_mutex(const char* szName) noexcept : m_handle(nullptr)
|
||||
{
|
||||
if (szName)
|
||||
m_ssName = szName;
|
||||
else
|
||||
{
|
||||
std::srand(static_cast<unsigned>(std::time(nullptr))); // Use current time as seed for random generator
|
||||
m_ssName = std::string("NAMED_MUTEX_") + std::to_string(std::rand());
|
||||
}
|
||||
m_handle = sem_open(m_ssName.c_str(), O_CREAT, 0777 /*O_RDWR*/, 1);
|
||||
}
|
||||
|
||||
inline named_mutex::named_mutex(named_mutex&& rmtx) noexcept : m_handle(rmtx.m_handle)
|
||||
{
|
||||
rmtx.m_handle = nullptr;
|
||||
}
|
||||
|
||||
inline named_mutex::~named_mutex()
|
||||
{
|
||||
if (m_handle)
|
||||
sem_close(m_handle);
|
||||
}
|
||||
|
||||
// False positive warning of CppCheck: m_handle is not assigned a value. This is not the case. Suppress the warning.
|
||||
// cppcheck-suppress operatorEqVarError
|
||||
inline named_mutex& named_mutex::operator=(named_mutex&& rmtx) noexcept
|
||||
{
|
||||
if (m_handle)
|
||||
sem_close(m_handle);
|
||||
m_handle = rmtx.m_handle;
|
||||
rmtx.m_handle = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline void named_mutex::lock()
|
||||
{
|
||||
if (m_handle)
|
||||
sem_wait(m_handle);
|
||||
}
|
||||
|
||||
inline bool named_mutex::try_lock()
|
||||
{
|
||||
return m_handle ? sem_trywait(m_handle) >= 0 : false;
|
||||
}
|
||||
|
||||
inline void named_mutex::unlock()
|
||||
{
|
||||
if (m_handle)
|
||||
sem_post(m_handle);
|
||||
}
|
||||
|
||||
inline const std::string& named_mutex::name() const
|
||||
{
|
||||
return m_ssName;
|
||||
}
|
||||
|
||||
inline named_mutex::native_handle_type named_mutex::native_handle()
|
||||
{
|
||||
return m_handle;
|
||||
}
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // !defined IPC_NAMED_MUTEX_H
|
||||
112
global/localmemmgr.h
Normal file
112
global/localmemmgr.h
Normal file
@@ -0,0 +1,112 @@
|
||||
#ifndef LOCAL_MEM_MGR_H
|
||||
#define LOCAL_MEM_MGR_H
|
||||
|
||||
#define SDV_CORE_H
|
||||
|
||||
#define NO_SDV_CORE_FUNC
|
||||
#include "../export/interfaces/core.h"
|
||||
#include "../export//support/interface_ptr.h"
|
||||
|
||||
#include <support/mem_access.h>
|
||||
#include <support/interface_ptr.h>
|
||||
|
||||
/**
|
||||
* \brief Local memory manager class allowing the use of the SDV support classes without having to start the framework.
|
||||
* \attention Do not use the local memory manager together with the framework's memory manager.
|
||||
*/
|
||||
class CLocalMemMgr : public sdv::core::IMemoryAlloc, public sdv::IInterfaceAccess,
|
||||
public sdv::internal::IInternalMemAlloc
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* \brief Constructor assigning this class to the local services.
|
||||
*/
|
||||
CLocalMemMgr()
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Destructor removing this class from the local services.
|
||||
*/
|
||||
~CLocalMemMgr()
|
||||
{}
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IInterfaceAccess)
|
||||
SDV_INTERFACE_ENTRY(sdv::core::IMemoryAlloc)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Allocate a memory block of the provided length. Overload of sdv::core::IMemoryAlloc::Allocate.
|
||||
* @param[in] uiLength The length of the memory block to allocate.
|
||||
* @return Smart pointer to the allocated memory or NULL when memory allocation was not possible.
|
||||
*/
|
||||
virtual sdv::pointer<uint8_t> Allocate(uint32_t uiLength) override
|
||||
{
|
||||
return sdv::internal::make_ptr<uint8_t>(this, uiLength);
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Allocate memory. Overload of sdv::internal::IInternalMemAlloc::Alloc.
|
||||
* @param[in] nSize The size of the memory to allocate (in bytes).
|
||||
* @return Pointer to the memory allocation or NULL when memory allocation failed.
|
||||
*/
|
||||
virtual void* Alloc(size_t nSize) override
|
||||
{
|
||||
void* p = malloc(nSize);
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reallocate memory. Overload of sdv::internal::IInternalMemAlloc::Realloc.
|
||||
* @param[in] pData Pointer to a previous allocation or NULL when no previous allocation was available.
|
||||
* @param[in] nSize The size of the memory to allocate (in bytes).
|
||||
* @return Pointer to the memory allocation or NULL when memory allocation failed.
|
||||
*/
|
||||
virtual void* Realloc(void* pData, size_t nSize) override
|
||||
{
|
||||
void* p = realloc(pData, nSize);
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Free a memory allocation. Overload of sdv::internal::IInternalMemAlloc::Free.
|
||||
* @param[in] pData Pointer to a previous allocation.
|
||||
*/
|
||||
virtual void Free(void* pData) override
|
||||
{
|
||||
free(pData);
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef NO_SDV_LOCAL_CORE_FUNC
|
||||
namespace sdv
|
||||
{
|
||||
namespace core
|
||||
{
|
||||
/**
|
||||
* @brief Access to the core.
|
||||
* @return Smart pointer to the core services interface.
|
||||
*/
|
||||
inline TInterfaceAccessPtr GetCore()
|
||||
{
|
||||
static CLocalMemMgr memmgr;
|
||||
return &memmgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access to specific interface of the core.
|
||||
* @tparam TInterface Type of interface to return.
|
||||
* @return Pointer to the interface or NULL when the interface was not exposed.
|
||||
*/
|
||||
template <typename TInterface>
|
||||
inline TInterface* GetCore()
|
||||
{
|
||||
return GetCore().GetInterface<TInterface>();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // !defined(LOCAL_MEM_MGR_H)
|
||||
638
global/path_match.h
Normal file
638
global/path_match.h
Normal file
@@ -0,0 +1,638 @@
|
||||
#ifndef WILDCARD_MATCH_H
|
||||
#define WILDCARD_MATCH_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <regex>
|
||||
#include <stdexcept>
|
||||
#include <stack>
|
||||
#include <list>
|
||||
|
||||
/**
|
||||
* @brief Check for a match between a relative path and a pattern containing zero or more wildcards.
|
||||
* @details Check for the relative path matching a relative path-pattern possibly containing wildcard patterns. The following rules
|
||||
* to patterns apply:
|
||||
* - Definition of path-part: paths are split into directories optionally followed by a filename. As separator '/' is used.
|
||||
* - A directory name of '.' has a special meaning directing to the current directory.
|
||||
* - A directory name of '..' has a special meaning directing to a directory one up.
|
||||
* - Directory and file name can be separated with dots (for example "dir1.2/file0.bin").
|
||||
* - Within the path-part following zero or more characters, use '?' for any one character that might fit. The '?' might be
|
||||
* followed by zero or more characters and wildcards.
|
||||
* - Within the path-part following zero or more characters, use '*' for zero or more character until the next character or
|
||||
* separator. The '*' might be followed by zero or more characters and wildcards.
|
||||
* - It should be possible to use a directory as a pattern (without the file name) - then all the files of the directory are
|
||||
* matching (but not the files of the subdirectories).
|
||||
* - If the path-part equals '**', zero or more path-parts can be skipped to fit the rest of the path. This rule doesn't apply if
|
||||
* the part-part equals '*' and is the last part in the path.
|
||||
* Examples are:
|
||||
* - "subdir1" - directory only; all files within the directory (but not any sub-directories) match.
|
||||
* - "subdir1" - all files within the directory match.
|
||||
* - "subdir2/file2?.*n" - all files located in subdir2 starting with "file2" followed by a single digit or character with the
|
||||
* extension ending with "n" match.
|
||||
* - "subdir?_*" - all files from a directory starting with the name "subdir" followed by a single digit or character, followed
|
||||
* with a "_" and zero or more characters.
|
||||
* - "**\/file*.bin" - all files starting with the name "file" followed by zero or more characters and the extension ".bin" in this
|
||||
* and any subdirectory.
|
||||
* @param[in] rpathRel Reference to the relative path to check for a match.
|
||||
* @param[in] rssPattern Reference to the string containing the pattern to match.
|
||||
* @return Returns whether the path matches.
|
||||
*/
|
||||
inline bool CheckWildcardPathMatch(const std::filesystem::path& rpathRel, const std::string& rssPattern)
|
||||
{
|
||||
std::filesystem::path pathPattern = std::filesystem::u8path(rssPattern);
|
||||
auto itPathRel = rpathRel.begin();
|
||||
auto itPathPattern = pathPattern.begin();
|
||||
|
||||
// Iterate through the paths
|
||||
bool bLastPartAsterisk = false; // Special case when the last checked part is a single asterisk.
|
||||
while (itPathRel != rpathRel.end() && itPathPattern != pathPattern.end())
|
||||
{
|
||||
// Get the parts and increase
|
||||
std::string ssPathPart = itPathRel->generic_u8string();
|
||||
std::string ssPatternPart = itPathPattern->generic_u8string();
|
||||
itPathRel++;
|
||||
itPathPattern++;
|
||||
|
||||
// Compose a new path from the rest of the path starting at the provided iterator.
|
||||
auto fnBuildNewPath = [](std::filesystem::path::iterator& ritPath, const std::filesystem::path& rpath)
|
||||
{
|
||||
std::filesystem::path pathNew;
|
||||
while (ritPath != rpath.end())
|
||||
{
|
||||
pathNew /= *ritPath;
|
||||
++ritPath;
|
||||
}
|
||||
return pathNew;
|
||||
};
|
||||
|
||||
// Special case... skipping zero or more path parts.
|
||||
if (ssPatternPart == "**")
|
||||
{
|
||||
// The pattern "**/.." or even multiple higher directories like "**/../.." would indicate that any number of directories
|
||||
// can be skipped, but at least the amount of directories need to be present to be able to go higher. By building
|
||||
// potential relative paths (below), skip the amount of paths going higher.
|
||||
size_t nSkipParent = 0;
|
||||
while (itPathPattern != pathPattern.end() && itPathPattern->generic_u8string() == "..")
|
||||
{
|
||||
itPathPattern++;
|
||||
nSkipParent++;
|
||||
}
|
||||
|
||||
// Build a new pattern from the rest of the pattern.
|
||||
std::filesystem::path pathNewPattern = fnBuildNewPath(itPathPattern, pathPattern);
|
||||
|
||||
// Build a list of new potential relative paths.
|
||||
// NOTE: More efficient would be to execute the matching algorithm for each path-part until the end of the path.
|
||||
// Since the path iterator doesn't allow copying like it does with the container classes, it doesn't work to reuse the
|
||||
// ierator to build each new path on the fly. Instead, first create a list of potential paths and then execute the
|
||||
// matching process.
|
||||
std::vector<std::filesystem::path> vecNewRelPaths;
|
||||
itPathRel--; // Include the current path part as well.
|
||||
while (itPathRel != rpathRel.end())
|
||||
{
|
||||
// Skip the amount of parent parts if ".." was detected.
|
||||
if (nSkipParent)
|
||||
{
|
||||
itPathRel++;
|
||||
nSkipParent--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add to all existing paths in the vector
|
||||
for (auto& rpath : vecNewRelPaths)
|
||||
rpath /= *itPathRel;
|
||||
|
||||
// Add as a separate part to the vector
|
||||
vecNewRelPaths.push_back(*itPathRel);
|
||||
|
||||
// Next part
|
||||
itPathRel++;
|
||||
}
|
||||
|
||||
// When the amount of parents to be skipped is higher than 0, there is no parent to skip anymore.
|
||||
if (nSkipParent) return false;
|
||||
|
||||
// Iterate through the list of potential relative paths and check for at least one fit...
|
||||
for (const auto& rpathNewRel : vecNewRelPaths)
|
||||
{
|
||||
// Check for this path. If it matches... it is valid.
|
||||
if (CheckWildcardPathMatch(rpathNewRel, pathNewPattern.generic_u8string()))
|
||||
return true;
|
||||
}
|
||||
|
||||
// If none fit, there is no match.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Special case... ignore current test.
|
||||
if (ssPatternPart == ".")
|
||||
{
|
||||
// Redo the last path iteration and continue with the matching process.
|
||||
itPathRel--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Special case... use parent path.
|
||||
if (ssPatternPart == "..")
|
||||
{
|
||||
// If the path also has a higher directory, everything is good
|
||||
if (ssPathPart == "..") continue;
|
||||
|
||||
// Redo the last path iteration.
|
||||
itPathRel--;
|
||||
|
||||
// Does a higher part path exists?
|
||||
if (itPathRel == rpathRel.begin())
|
||||
{
|
||||
// A higher directory doesn't exist. Create a new pattern starting with "..".
|
||||
std::filesystem::path pathNewPattern = fnBuildNewPath(itPathPattern, pathPattern);
|
||||
|
||||
// Create a new path from the path starting with "..".
|
||||
std::filesystem::path pathNew = ".." / rpathRel;
|
||||
|
||||
// Redo the matching process.
|
||||
// NOTE: Since ".." was prepended, only generic patterns with "*" and "**" can match.
|
||||
return CheckWildcardPathMatch(pathNew, pathNewPattern.generic_u8string());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Redo the path before the last path iteration as well.
|
||||
itPathRel--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Special case... skipping the current part
|
||||
if (ssPatternPart == "*")
|
||||
{
|
||||
// Are there any pattern parts following; if not, the last pattern part is an asterisk.
|
||||
if (itPathPattern == pathPattern.end())
|
||||
bLastPartAsterisk = true;
|
||||
else if (itPathPattern->generic_u8string() == "..")
|
||||
{
|
||||
// The combination "*/.." cancels itself.
|
||||
itPathRel--;
|
||||
itPathPattern++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through the path parts
|
||||
size_t nPathPartPos = 0;
|
||||
size_t nPatternPartPos = 0;
|
||||
while (nPathPartPos != ssPathPart.size() && nPatternPartPos != ssPatternPart.size())
|
||||
{
|
||||
// Get the characters and increase
|
||||
char cPathPart = ssPathPart[nPathPartPos];
|
||||
char cPatternPart = ssPatternPart[nPatternPartPos];
|
||||
nPathPartPos++;
|
||||
nPatternPartPos++;
|
||||
|
||||
// Classify and check with the pattern.
|
||||
switch (cPatternPart)
|
||||
{
|
||||
case '?':
|
||||
// Any character is allowed.
|
||||
break;
|
||||
case '*':
|
||||
// Skip zero or more wildcard-characters following the asterisk.
|
||||
while (nPatternPartPos != ssPatternPart.size() &&
|
||||
(ssPatternPart[nPatternPartPos] == '*' || ssPatternPart[nPatternPartPos] == '?'))
|
||||
nPatternPartPos++;
|
||||
|
||||
// When there are no more characters following, the rest of the path part fits.
|
||||
if (nPatternPartPos == ssPatternPart.size())
|
||||
{
|
||||
nPathPartPos = ssPathPart.size();
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip zero or more characters in the path part until the next fitting characters.
|
||||
cPatternPart = ssPatternPart[nPatternPartPos];
|
||||
nPathPartPos--; // Start with the current path
|
||||
while (nPathPartPos != ssPathPart.size() && ssPathPart[nPathPartPos] != cPatternPart)
|
||||
nPathPartPos++;
|
||||
|
||||
// When the path part pos has reached the end, there is no match.
|
||||
if (nPathPartPos == ssPathPart.size())
|
||||
return false;
|
||||
|
||||
// Go to the next character
|
||||
nPathPartPos++;
|
||||
nPatternPartPos++;
|
||||
break;
|
||||
default:
|
||||
// Character needs to fit
|
||||
if (cPathPart != cPatternPart) return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the path part has reached the end, but the pattern still has space, this might indicate a mismatch. But only when the
|
||||
// rest of the pattern doesn't contain one or more asterisk '*' until the end of the path. Simply skip these characters.
|
||||
while (nPatternPartPos != ssPatternPart.size() && ssPatternPart[nPatternPartPos] == '*')
|
||||
nPatternPartPos++;
|
||||
|
||||
// When both have reached the end, the path parts are identical.
|
||||
if (nPathPartPos == ssPathPart.size() && nPatternPartPos == ssPatternPart.size())
|
||||
continue;
|
||||
|
||||
// No match
|
||||
return false;
|
||||
}
|
||||
|
||||
// When both have reached the end of iteration, we have a match of files.
|
||||
if (itPathRel == rpathRel.end() && itPathPattern == pathPattern.end())
|
||||
return true;
|
||||
|
||||
// When only itPathPattern has reached the end of iteration, we have a match of directories... All files in the directory (but
|
||||
// not in any sub-directories) are valid. The exception would be if the pattern was "*", which is forced here to match files
|
||||
// only. In that case bLastPartAsterisk would be true.
|
||||
if (itPathRel != rpathRel.end() && itPathPattern == pathPattern.end() && !bLastPartAsterisk)
|
||||
{
|
||||
itPathRel++;
|
||||
return itPathRel == rpathRel.end();
|
||||
}
|
||||
|
||||
// No match...
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Normalizes a regex path pattern string.
|
||||
*
|
||||
* This function processes a path string to resolve current ('.') and parent ('..')
|
||||
* directory indicators according to standard path canonicalization rules, plus
|
||||
* custom rules for regex generation.
|
||||
*
|
||||
* Rules applied:
|
||||
* 1. Path segments of '.' are removed (e.g., "a/./b" -> "a/b").
|
||||
* 2. A directory followed by '..' is resolved (e.g., "a/b/../c" -> "a/c"). Special case if the parent is any directory ".*\\.\\./"
|
||||
* and ".*\/\\.\\./". Both will result in any directory ".*\/".
|
||||
* 3. If a path attempts to navigate above the root (e.g., "../a" or "a/../../b"), this only works if the following directory is
|
||||
* any directory (starts with ".*").
|
||||
* @param[in] rssPattern Reference to the regex pattern string to normalize.
|
||||
* @return The normalized regex pattern string.
|
||||
*/
|
||||
inline std::string NormalizeRegexPattern(const std::string& rssPattern)
|
||||
{
|
||||
// Throw regex group mismatch exception
|
||||
auto fnThrowGroupException = [](char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '(':
|
||||
case ')':
|
||||
throw std::regex_error(std::regex_constants::error_paren);
|
||||
case '[':
|
||||
case ']':
|
||||
throw std::regex_error(std::regex_constants::error_brack);
|
||||
case '{':
|
||||
case '}':
|
||||
throw std::regex_error(std::regex_constants::error_brace);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Return the matching group character (parenthesis, brakcets or braces)
|
||||
auto fnGroupMatch = [](char cOpenGroup) -> char
|
||||
{
|
||||
switch (cOpenGroup)
|
||||
{
|
||||
case '(': return ')';
|
||||
case ')': return '(';
|
||||
case '[': return ']';
|
||||
case ']': return '[';
|
||||
case '{': return '}';
|
||||
case '}': return '{';
|
||||
default: return cOpenGroup;
|
||||
}
|
||||
};
|
||||
|
||||
// Split the pattern in chunks
|
||||
std::list<std::string> lstPattern;
|
||||
std::string ssPart;
|
||||
size_t nPos = 0;
|
||||
std::stack<char> stackGroup;
|
||||
while (nPos < rssPattern.size())
|
||||
{
|
||||
char c = rssPattern[nPos];
|
||||
nPos++;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '(': // Group (...)
|
||||
case '[': // Group [...]
|
||||
case '{': // Group {...}
|
||||
stackGroup.push(c);
|
||||
ssPart += c;
|
||||
break;
|
||||
case ')': // Group (...)
|
||||
case ']': // Group [...]
|
||||
case '}': // Group {...}
|
||||
if (stackGroup.empty()) fnThrowGroupException(c);
|
||||
if (fnGroupMatch(stackGroup.top()) != c) fnThrowGroupException(stackGroup.top());
|
||||
stackGroup.pop();
|
||||
ssPart += c;
|
||||
break;
|
||||
case '\\': // Escape
|
||||
ssPart += c;
|
||||
if (nPos >= rssPattern.size()) throw std::regex_error(std::regex_constants::error_escape);
|
||||
c = rssPattern[nPos];
|
||||
ssPart += c;
|
||||
nPos++;
|
||||
break;
|
||||
case '/': // Separator
|
||||
ssPart += c;
|
||||
if (stackGroup.empty())
|
||||
{
|
||||
lstPattern.push_back(std::move(ssPart));
|
||||
ssPart.clear();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ssPart += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!stackGroup.empty()) fnThrowGroupException(stackGroup.top());
|
||||
if (!ssPart.empty())
|
||||
lstPattern.push_back(std::move(ssPart));
|
||||
|
||||
// Erase all entries to the current directory
|
||||
lstPattern.erase(std::remove(lstPattern.begin(), lstPattern.end(), "\\./"), lstPattern.end());
|
||||
|
||||
// Handle parent directories
|
||||
auto itPart = lstPattern.begin();
|
||||
while (itPart != lstPattern.end())
|
||||
{
|
||||
// Next
|
||||
auto itCurrent = itPart;
|
||||
itPart++;
|
||||
|
||||
// Check for any directory followed by a parent directory - change to any directory: ".*\\.\\./"
|
||||
if (*itCurrent == ".*\\.\\./")
|
||||
{
|
||||
*itCurrent = ".*/";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for parent directory
|
||||
if (*itCurrent == "\\.\\./")
|
||||
{
|
||||
// Check whether there is a directory before
|
||||
if (itCurrent != lstPattern.begin())
|
||||
{
|
||||
// Get the directory before
|
||||
auto itBefore = itCurrent;
|
||||
itBefore--;
|
||||
|
||||
// Anything with .* in it will be truncated: "abc.*/", ".*jojo/", ".*/"
|
||||
nPos = itBefore->rfind(".*");
|
||||
if (nPos != std::string::npos)
|
||||
{
|
||||
*itBefore = itBefore->substr(nPos, 2) + '/';
|
||||
lstPattern.erase(itCurrent);
|
||||
continue;
|
||||
}
|
||||
|
||||
// All others are considered a single directory and will be removed
|
||||
lstPattern.erase(itBefore);
|
||||
lstPattern.erase(itCurrent);
|
||||
continue;
|
||||
}
|
||||
else if (itPart != lstPattern.end())
|
||||
{
|
||||
// Get the directory behind
|
||||
auto itBehind = itPart;
|
||||
itPart++;
|
||||
|
||||
// Get the directory after. This can only work if the directory after can be anything: starting with ".*/", ".*anything" or "[^/]+/"
|
||||
if (*itBehind == ".*/")
|
||||
{
|
||||
lstPattern.erase(itCurrent);
|
||||
*itBehind = ".*"; // Remove trailing slash
|
||||
continue;
|
||||
}
|
||||
if (itBehind->substr(0, 2) == ".*")
|
||||
{
|
||||
lstPattern.erase(itCurrent);
|
||||
continue;
|
||||
}
|
||||
else if (*itBehind == "[^/]+/")
|
||||
{
|
||||
lstPattern.erase(itCurrent);
|
||||
lstPattern.erase(itBehind);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Invalid use of "\\.\\."
|
||||
throw std::regex_error(std::regex_constants::error_complexity);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalid use of "\\.\\."
|
||||
throw std::regex_error(std::regex_constants::error_complexity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the pattern
|
||||
std::string ssPatternNew;
|
||||
for (const std::string& rssPart : lstPattern)
|
||||
ssPatternNew += rssPart;
|
||||
|
||||
return ssPatternNew;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a path matches a given regex pattern.
|
||||
* @details The matching is performed against the entire path.
|
||||
* @remarks For the regex patterns, it is assumed that navigation directories like "." and ".." are to be matched as literal text.
|
||||
* Therefore, in the regex pattern, dots should be escaped (e.g., `\.` and `\.\.`).
|
||||
* @param[in] rpathRel Reference to the existing relative filesystem path to check.
|
||||
* @param[in] rssPattern Reference to the pattern expressed as a regular expression.
|
||||
* @return Returns if the path matches the pattern.
|
||||
*/
|
||||
inline bool CheckRegexPathMatch(const std::filesystem::path& rpathRel, const std::string& rssPattern)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Convert paths to generic, portable string format (with '/' separators)
|
||||
const std::string ssPath = rpathRel.generic_u8string();
|
||||
|
||||
// Use the ECMAScript grammar, which is the default and most common.
|
||||
const std::regex rgx(NormalizeRegexPattern(rssPattern), std::regex::ECMAScript);
|
||||
|
||||
// std::regex_match checks if the entire string is a match for the pattern.
|
||||
return std::regex_match(ssPath, rgx);
|
||||
}
|
||||
catch (const std::regex_error& e)
|
||||
{
|
||||
// In case of an invalid regex pattern, log the error and return false.
|
||||
std::cerr << "Regex error: " << e.what() << " for pattern: " << rssPattern << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Algorithm to be used for matching the path to the pattern.
|
||||
*/
|
||||
enum class EPathMatchAlgorithm
|
||||
{
|
||||
wildcards, ///< Use wildcards like '?', '*' and '**'
|
||||
regex, ///< Use regular expressins
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Collect a vector of paths from a wildcard pattern.
|
||||
* @param[in] rpathBase Reference to the base path to start collecting.
|
||||
* @param[in] rssPattern Reference to the string containing the pattern to match files with.
|
||||
* @param[in] eAlgorithm The algorithm to use for path collection.
|
||||
* @return Returns a vector with paths that are matching the pattern.
|
||||
*/
|
||||
inline std::vector<std::filesystem::path> CollectWildcardPath(const std::filesystem::path& rpathBase,
|
||||
const std::string& rssPattern, EPathMatchAlgorithm eAlgorithm = EPathMatchAlgorithm::wildcards)
|
||||
{
|
||||
std::vector<std::filesystem::path> pathMatches;
|
||||
try
|
||||
{
|
||||
std::filesystem::path pathBaseCopy = rpathBase;
|
||||
std::string ssPatternCopy = rssPattern;
|
||||
bool bUseAbsolutePath = false;
|
||||
if (pathBaseCopy.empty())
|
||||
{
|
||||
// Get the first position of a path not including wildcard and/or regex special characters
|
||||
size_t nPos = rssPattern.find_first_of("[](){}*?.\\+");
|
||||
|
||||
// Get the directory separator before that
|
||||
nPos = rssPattern.substr(0, nPos).find_last_of('/');
|
||||
|
||||
// The base pattern is everything before the '/'.
|
||||
if (nPos != std::string::npos)
|
||||
{
|
||||
pathBaseCopy = rssPattern.substr(0, nPos);
|
||||
ssPatternCopy = rssPattern.substr(nPos + 1);
|
||||
bUseAbsolutePath = true;
|
||||
}
|
||||
}
|
||||
else if (std::filesystem::path(rssPattern).is_absolute())
|
||||
{
|
||||
// Separator processing function.
|
||||
size_t nSearchPos = 0;
|
||||
auto fnProcessSeparator = [&rssPattern, &nSearchPos]() -> bool
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (nSearchPos >= rssPattern.size() || (rssPattern[nSearchPos] != '/' && rssPattern[nSearchPos] != '\\'))
|
||||
#else
|
||||
if (nSearchPos >= rssPattern.size() || rssPattern[nSearchPos] != '/')
|
||||
#endif
|
||||
return false;
|
||||
nSearchPos++;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Remove the base from the pattern.
|
||||
bool bExplicitSeparator = false;
|
||||
for (const auto& rpathBasePart : rpathBase)
|
||||
{
|
||||
// Skip separator in base path
|
||||
#ifdef _WIN32
|
||||
if (rpathBasePart == "/" || rpathBasePart == "\\")
|
||||
#else
|
||||
if (rpathBasePart == "/")
|
||||
#endif
|
||||
{
|
||||
fnProcessSeparator();
|
||||
bExplicitSeparator = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Separator needed?
|
||||
if (!bExplicitSeparator && nSearchPos && !fnProcessSeparator())
|
||||
return {};
|
||||
bExplicitSeparator = false;
|
||||
|
||||
// Check for the base part inside the pattern
|
||||
std::string ssPathPart = rpathBasePart.generic_u8string();
|
||||
if (rssPattern.substr(nSearchPos, ssPathPart.size()) != ssPathPart)
|
||||
return {};
|
||||
nSearchPos += ssPathPart.size();
|
||||
}
|
||||
|
||||
// Skip trailing slash if existing
|
||||
#ifdef _WIN32
|
||||
if (nSearchPos < rssPattern.size() &&
|
||||
(rssPattern[nSearchPos] == '/' || (rssPattern[nSearchPos] == '\\' && eAlgorithm == EPathMatchAlgorithm::wildcards)))
|
||||
#else
|
||||
if (nSearchPos < rssPattern.size() && rssPattern[nSearchPos] == '/')
|
||||
#endif
|
||||
nSearchPos++;
|
||||
|
||||
// Copy the pattern following the parg
|
||||
ssPatternCopy = rssPattern.substr(nSearchPos);
|
||||
|
||||
// If the pattern was absolute and the represents exactly the base path, search for all files in the top most directory.
|
||||
if (ssPatternCopy.empty())
|
||||
{
|
||||
// Add wildcards or regex structure for all files in all directories
|
||||
switch (eAlgorithm)
|
||||
{
|
||||
case EPathMatchAlgorithm::regex:
|
||||
ssPatternCopy = "[^/]+";
|
||||
break;
|
||||
case EPathMatchAlgorithm::wildcards:
|
||||
default:
|
||||
ssPatternCopy = "*";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no pattern, then all content of the directory of the base path is required.
|
||||
if (ssPatternCopy.empty())
|
||||
{
|
||||
// Add wildcards or regex structure for all files in all directories
|
||||
switch (eAlgorithm)
|
||||
{
|
||||
case EPathMatchAlgorithm::regex:
|
||||
ssPatternCopy = ".*";
|
||||
break;
|
||||
case EPathMatchAlgorithm::wildcards:
|
||||
default:
|
||||
ssPatternCopy = "**";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through the path files of the base path.
|
||||
for (auto const& rDirEntry : std::filesystem::recursive_directory_iterator(pathBaseCopy))
|
||||
{
|
||||
if (!rDirEntry.is_regular_file()) continue;
|
||||
std::filesystem::path pathRel = rDirEntry.path().lexically_relative(pathBaseCopy);
|
||||
std::filesystem::path pathToAdd = bUseAbsolutePath ? rDirEntry.path() : pathRel;
|
||||
switch (eAlgorithm)
|
||||
{
|
||||
case EPathMatchAlgorithm::wildcards:
|
||||
if (CheckWildcardPathMatch(pathRel, ssPatternCopy))
|
||||
pathMatches.push_back(pathToAdd);
|
||||
break;
|
||||
case EPathMatchAlgorithm::regex:
|
||||
if (CheckRegexPathMatch(pathRel, ssPatternCopy))
|
||||
pathMatches.push_back(pathToAdd);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::filesystem::filesystem_error& rexception)
|
||||
{
|
||||
std::cerr << rexception.what();
|
||||
}
|
||||
return pathMatches;
|
||||
}
|
||||
|
||||
#endif // WILDCARD_MATCH_H
|
||||
104
global/process_watchdog.h
Normal file
104
global/process_watchdog.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#ifndef PROCESS_WATCHDOG_H
|
||||
#define PROCESS_WATCHDOG_H
|
||||
|
||||
#include <thread>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#ifdef _WIN32
|
||||
// Prevent reassignment of "interface"
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
// Include windows headers
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <objbase.h>
|
||||
|
||||
// Use previous assignment of "interface"
|
||||
#pragma pop_macro("interface")
|
||||
|
||||
// Remove "GetObject" assignment
|
||||
#ifdef GetObject
|
||||
#undef GetObject
|
||||
#endif
|
||||
|
||||
// Remove "GetClassInfo" assignment
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
|
||||
#elif defined __unix__
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#error The OS is not supported!
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Process watchdog class; ends the process when runtime duration has superseded (as is the case when a deadlock has
|
||||
* occurred).
|
||||
*/
|
||||
class CProcessWatchdog
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] iWatchdogTimeS The duration the watchdog is monitoring before process termination in seconds (default 300s).
|
||||
*/
|
||||
CProcessWatchdog(int64_t iWatchdogTimeS = 300ll)
|
||||
{
|
||||
m_threadWatchdog = std::thread(&CProcessWatchdog::WatchdogThreadFunc, this, iWatchdogTimeS );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor; cancels the watch thread.
|
||||
*/
|
||||
~CProcessWatchdog()
|
||||
{
|
||||
m_bTerminateWatchdog = true;
|
||||
if (m_threadWatchdog.joinable())
|
||||
m_threadWatchdog.join();
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Watch thread function.
|
||||
* @param[in] iWatchdogTimeS The duration the watchdog is monitoring before process termination in seconds (default 120s).
|
||||
*/
|
||||
void WatchdogThreadFunc(int64_t iWatchdogTimeS = 120ll)
|
||||
{
|
||||
// Run for the most the set time; then terminate...
|
||||
auto tpStart = std::chrono::steady_clock::now();
|
||||
while (!m_bTerminateWatchdog)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
auto tpNow = std::chrono::steady_clock::now();
|
||||
if (std::chrono::duration_cast<std::chrono::seconds>(tpNow - tpStart).count() > iWatchdogTimeS)
|
||||
{
|
||||
std::cerr << "WATCHDOG TERMINATION ENFORCED!!!" << std::endl;
|
||||
std::cerr.flush();
|
||||
#ifdef _WIN32
|
||||
// Get the current process handle
|
||||
HANDLE hProcess = GetCurrentProcess();
|
||||
// Terminate the current process with exit code -100
|
||||
TerminateProcess(hProcess, static_cast<UINT>(-100));
|
||||
#elif defined __unix__
|
||||
|
||||
// Send SIGTERM signal to the current process
|
||||
kill(getpid(), SIGTERM);
|
||||
#else
|
||||
#error The OS is not supported!
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool m_bTerminateWatchdog = false; ///< When set, allows the thread to terminate.
|
||||
std::thread m_threadWatchdog; ///< The watchdog thread.
|
||||
};
|
||||
|
||||
#endif // !defined PROCESS_WATCHDOG_H
|
||||
221
global/scheduler/scheduler.cpp
Normal file
221
global/scheduler/scheduler.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
#include "scheduler.h"
|
||||
#include <cassert>
|
||||
|
||||
CTaskScheduler::CTaskScheduler(size_t nMinIdle /*= 4*/, size_t nMaxBusy /*= 32*/) :
|
||||
m_nMinIdle(nMinIdle), m_nMaxBusy(nMaxBusy)
|
||||
{
|
||||
// Check min/max values
|
||||
assert(nMinIdle > 0);
|
||||
if (!m_nMinIdle) m_nMinIdle = 1;
|
||||
assert(nMinIdle > 0);
|
||||
if (!m_nMaxBusy) m_nMaxBusy = 1;
|
||||
|
||||
// Start the minimal required idle threads
|
||||
for (size_t n = 0; n < nMinIdle; n++)
|
||||
m_queueIdleThreads.push(
|
||||
m_lstThreads.insert(m_lstThreads.end(), std::make_shared<CThread>()));
|
||||
m_nMaxThreads = m_lstThreads.size();
|
||||
}
|
||||
|
||||
CTaskScheduler::~CTaskScheduler()
|
||||
{
|
||||
WaitForExecution();
|
||||
}
|
||||
|
||||
bool CTaskScheduler::Schedule(std::function<void()> fnTask, hlpr::flags<EScheduleFlags> flags /*= 0*/)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
|
||||
// Get a thread - either from the idle queue or new.
|
||||
std::list<std::shared_ptr<CThread>>::iterator itThread = m_lstThreads.end();
|
||||
if (m_queueIdleThreads.empty())
|
||||
{
|
||||
// No thread is available. Allowed to make one?
|
||||
if (m_lstThreads.size() < m_nMaxBusy)
|
||||
itThread = m_lstThreads.insert(m_lstThreads.end(), std::make_shared<CThread>());
|
||||
if (m_lstThreads.size() > m_nMaxThreads)
|
||||
m_nMaxThreads = m_lstThreads.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
itThread = m_queueIdleThreads.front();
|
||||
m_queueIdleThreads.pop();
|
||||
}
|
||||
|
||||
// Is there a valid thread? The schedule the task. Otherwise queue the task.
|
||||
if (itThread != m_lstThreads.end())
|
||||
{
|
||||
lock.unlock();
|
||||
Execute(itThread, fnTask);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Queuing allowed?
|
||||
if (flags & EScheduleFlags::no_queue) return false; // No scheduling possible.
|
||||
|
||||
// Add the task to the queue
|
||||
if (flags & EScheduleFlags::priority)
|
||||
m_dequeTasks.push_front(fnTask);
|
||||
else
|
||||
m_dequeTasks.push_back(fnTask);
|
||||
}
|
||||
|
||||
// Successful scheduled.
|
||||
return true;
|
||||
}
|
||||
|
||||
void CTaskScheduler::WaitForExecution()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
|
||||
// Temporarily reduce the idle level to zero.
|
||||
size_t nMinIdleTemp = m_nMinIdle;
|
||||
m_nMinIdle = 0;
|
||||
|
||||
// Wait until the thread list is empty.
|
||||
while (!m_lstThreads.empty())
|
||||
{
|
||||
// Erase all idle threads.
|
||||
while (!m_queueIdleThreads.empty())
|
||||
{
|
||||
// False positive of CppCheck: content of m_queueIdleThreads represents the iterator of m_lstThreads
|
||||
// cppcheck-suppress mismatchingContainerIterator
|
||||
m_lstThreads.erase(m_queueIdleThreads.front());
|
||||
m_queueIdleThreads.pop();
|
||||
}
|
||||
|
||||
// Allow execution threads to finalize its processing
|
||||
lock.unlock();
|
||||
|
||||
// Wait shortly
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
// Check again
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
// Restore the idle thread amount
|
||||
m_nMinIdle = nMinIdleTemp;
|
||||
}
|
||||
|
||||
size_t CTaskScheduler::GetThreadCount() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
return m_lstThreads.size();
|
||||
}
|
||||
|
||||
size_t CTaskScheduler::GetMaxThreadCount() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
return m_nMaxThreads;
|
||||
}
|
||||
|
||||
size_t CTaskScheduler::GetBusyThreadCount() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
return m_lstThreads.size() - m_queueIdleThreads.size();
|
||||
}
|
||||
|
||||
size_t CTaskScheduler::GetIdleThreadCount() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
return m_queueIdleThreads.size();
|
||||
}
|
||||
|
||||
void CTaskScheduler::Execute(std::list<std::shared_ptr<CThread>>::iterator itThread, std::function<void()> fnTask)
|
||||
{
|
||||
std::shared_ptr<CThread>& rptrThread = *itThread;
|
||||
|
||||
// Call execute.
|
||||
rptrThread->Execute([this, fnTask, itThread]()
|
||||
{
|
||||
// Start with the supplied task.
|
||||
std::function<void()> fnTaskLocal = fnTask;
|
||||
|
||||
// Execute tasks for as long as there are any
|
||||
do
|
||||
{
|
||||
fnTaskLocal();
|
||||
|
||||
// Any other task?
|
||||
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
|
||||
if (m_dequeTasks.empty()) break;
|
||||
fnTaskLocal = std::move(m_dequeTasks.front());
|
||||
m_dequeTasks.pop_front();
|
||||
|
||||
} while (true);
|
||||
|
||||
|
||||
// Prepare for adding this thread to the idle queue. Remove any redundant threads.
|
||||
// Attention: it is not possible to terminate this thread, since it still runs the thread func and relies on member
|
||||
// variables (which would otherwise be cleared). It is, however, possible to remove all other threads.
|
||||
std::unique_lock<std::mutex> lock2(m_mtxQueueAccess);
|
||||
while (!m_queueIdleThreads.empty() && m_queueIdleThreads.size() >= m_nMinIdle)
|
||||
{
|
||||
// False positive of CppCheck: content of m_queueIdleThreads represents the iterator of m_lstThreads
|
||||
// cppcheck-suppress mismatchingContainerIterator
|
||||
m_lstThreads.erase(m_queueIdleThreads.front());
|
||||
m_queueIdleThreads.pop();
|
||||
}
|
||||
|
||||
// Add this thread to the idle queue.
|
||||
m_queueIdleThreads.push(itThread);
|
||||
});
|
||||
}
|
||||
|
||||
CTaskScheduler::CThread::CThread()
|
||||
{
|
||||
// Wait for the start
|
||||
std::unique_lock<std::mutex> lockStart(m_mtxSyncStart);
|
||||
m_thread = std::thread(&CThread::ThreadFunc, this);
|
||||
while (!m_bStarted)
|
||||
m_cvStarted.wait_for(lockStart, std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
CTaskScheduler::CThread::~CThread()
|
||||
{
|
||||
// Lock to prevent execution still to take place. Then shutdown.
|
||||
std::unique_lock<std::mutex> lock(m_mtxSyncExecute);
|
||||
m_bShutdown = true;
|
||||
m_cvExecute.notify_all();
|
||||
lock.unlock();
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
void CTaskScheduler::CThread::Execute(std::function<void()> fnTask)
|
||||
{
|
||||
// Assign the task and notify for execution.
|
||||
std::unique_lock<std::mutex> lock(m_mtxSyncExecute);
|
||||
m_fnTask = fnTask;
|
||||
m_cvExecute.notify_all();
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
void CTaskScheduler::CThread::ThreadFunc()
|
||||
{
|
||||
// Thread has started (needed, since the condition variable doesn't keep its state).
|
||||
m_bStarted = true;
|
||||
|
||||
// Notify the thread has started
|
||||
std::unique_lock<std::mutex> lockStart(m_mtxSyncStart);
|
||||
m_cvStarted.notify_all();
|
||||
lockStart.unlock();
|
||||
|
||||
// Notify the thread has executed
|
||||
std::unique_lock<std::mutex> lockExecute(m_mtxSyncExecute);
|
||||
while (!m_bShutdown)
|
||||
{
|
||||
// Wait for an execution task to take place.
|
||||
m_cvExecute.wait_for(lockExecute, std::chrono::milliseconds(10));
|
||||
|
||||
// In case there is no task scheduled.
|
||||
if (!m_fnTask) continue;
|
||||
|
||||
// Execute the task
|
||||
m_fnTask();
|
||||
|
||||
// Task is done, clear the task function
|
||||
m_fnTask = {};
|
||||
}
|
||||
}
|
||||
143
global/scheduler/scheduler.h
Normal file
143
global/scheduler/scheduler.h
Normal file
@@ -0,0 +1,143 @@
|
||||
#ifndef THREAD_POOL_H
|
||||
#define THREAD_POOL_H
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include "../flags.h"
|
||||
|
||||
/**
|
||||
* @brief Job scheduler that uses a dynamic threadpool to schedule the task.
|
||||
*/
|
||||
class CTaskScheduler
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] nMinIdle The amount of idle threads that should stay present if there is nothing to process at the moment.
|
||||
* @param[in] nMaxBusy The maximum amount of threads that is used for processing.
|
||||
*/
|
||||
CTaskScheduler(size_t nMinIdle = 4, size_t nMaxBusy = 32);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CTaskScheduler();
|
||||
|
||||
/**
|
||||
* @brief Schedule flags to influence the scheduling.
|
||||
*/
|
||||
enum class EScheduleFlags
|
||||
{
|
||||
normal = 0x0, ///< If thread level threshold has been reached, queue the call at the end of the queue.
|
||||
priority = 0x1, ///< If thread level threshold has been reached, insert the call at the begin of the queue.
|
||||
no_queue = 0x2, ///< If thread level threshold has been reached, fail the schedule call.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Schedule the asynchronous execution of the task.
|
||||
* @details If an idle thread is available, the thread will execute the task. If no thread is available and the maximum thread
|
||||
* level hasn't been reached, a new thread will be started that schedules the task. If the maximum thread level has been
|
||||
* exceeded the task will be placed in the task list based on its priority and allowance. If queue is not allowed (set by the
|
||||
* no_queue flag), the scheduling will fail.
|
||||
* @param[in] fnTask The task to schedule.
|
||||
* @param[in] flags Zero or more flags to use for scheduling the task.
|
||||
* @return Returns whether the scheduling was successful or not.
|
||||
*/
|
||||
bool Schedule(std::function<void()> fnTask, hlpr::flags<EScheduleFlags> flags = EScheduleFlags::normal);
|
||||
|
||||
/**
|
||||
* @brief Wait until the execution of all threads has been finalized. This will also remove all idle threads.
|
||||
* @attention Do not call from a task function - that will cause a deadlock.
|
||||
*/
|
||||
void WaitForExecution();
|
||||
|
||||
/**
|
||||
* @brief Get the current amount of threads (idle + processing).
|
||||
* @return The amount of threads.
|
||||
*/
|
||||
size_t GetThreadCount() const;
|
||||
|
||||
/**
|
||||
* @brief Get the maximum amount of threads that were processing at one time.
|
||||
* @return The amount of threads.
|
||||
*/
|
||||
size_t GetMaxThreadCount() const;
|
||||
|
||||
/**
|
||||
* @brief Get the current amount of processing threads.
|
||||
* @return The amount of threads.
|
||||
*/
|
||||
size_t GetBusyThreadCount() const;
|
||||
|
||||
/**
|
||||
* @brief Get the current amount of idle threads.
|
||||
* @return The amount of threads.
|
||||
*/
|
||||
size_t GetIdleThreadCount() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Helper class for the thread scheduling
|
||||
*/
|
||||
struct CThread
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor starting the thread.
|
||||
*/
|
||||
CThread();
|
||||
|
||||
/**
|
||||
* @brief Destructor stopping the thread.
|
||||
*/
|
||||
~CThread();
|
||||
|
||||
/**
|
||||
* @brief Schedule an execution.
|
||||
* @param[in] fnTask The execution task.
|
||||
*/
|
||||
void Execute(std::function<void()> fnTask);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief The thread function to execute.
|
||||
*/
|
||||
void ThreadFunc();
|
||||
|
||||
std::thread m_thread; ///< The thread that executes the tasks.
|
||||
bool m_bShutdown = false; ///< Set when the thread should terminate.
|
||||
bool m_bStarted = false; ///< Set when the thread has started.
|
||||
std::function<void()> m_fnTask; ///< The task to execute (will be updated with new tasks before execution).
|
||||
std::mutex m_mtxSyncStart; ///< The startup synchronization mutex.
|
||||
std::condition_variable m_cvStarted; ///< Triggered by the thread to indicate that it has started.
|
||||
std::mutex m_mtxSyncExecute; ///< The execute synchronization mutex.
|
||||
std::condition_variable m_cvExecute; ///< Triggers the thread to indicate that there is a task to execute or
|
||||
///< shutdown has been requested.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Execute a task using the provided thread. After the execution, other tasks from the task list will be executed as
|
||||
* well. If after all executions are finalized and if there are enough idle threads, removes the thread from
|
||||
* the list. Otherwise adds the thread to the idle queue.
|
||||
* @param[in] itThread The thread to use for the execution.
|
||||
* @param[in] fnTask The task to execute.
|
||||
*/
|
||||
void Execute(std::list<std::shared_ptr<CThread>>::iterator itThread, std::function<void()> fnTask);
|
||||
|
||||
size_t m_nMinIdle = 4; ///< The minimal required amount of idle threads
|
||||
size_t m_nMaxBusy = 32; ///< The maximum allowed amount of busy threads
|
||||
size_t m_nMaxThreads = 0; ///< The maximum amount threads at the same time.
|
||||
mutable std::mutex m_mtxQueueAccess; ///< Sync access to queue, list and double-ended-queue.
|
||||
std::queue<std::list<std::shared_ptr<CThread>>::iterator> m_queueIdleThreads; ///< Idle thread queue.
|
||||
std::list<std::shared_ptr<CThread>> m_lstThreads; ///< List with all threads.
|
||||
std::deque<std::function<void()>> m_dequeTasks; ///< Double ended task queue.
|
||||
};
|
||||
|
||||
#endif // !defined THREAD_POOL_H
|
||||
268
global/timetracker.h
Normal file
268
global/timetracker.h
Normal file
@@ -0,0 +1,268 @@
|
||||
#ifndef TIME_TRACKER_H
|
||||
#define TIME_TRACKER_H
|
||||
|
||||
#include "exec_dir_helper.h"
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <fstream>
|
||||
|
||||
/**
|
||||
* @brief Track function to trigger one time entry.
|
||||
* @param tag The tag name to use to identify this entry.
|
||||
* @details Use the "track_time" macro within a function to activate. The tag is a name used to identify the code position that
|
||||
* should be tracked. For each track_time call, a time entry relative to the start of the application will be written (resolution
|
||||
* is micro-seconds). The tracking file will be written on shutdown of the application and is called the same as the application
|
||||
* with "_tracking.csv" attached to it.
|
||||
* Example:
|
||||
* @code
|
||||
* void MyFunc()
|
||||
* {
|
||||
* track_time(enter);
|
||||
*
|
||||
* // ...
|
||||
*
|
||||
* track_timer(leave);
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
#define track_time(tag) GetTimeTracker().Trigger(__FUNCTION__, #tag)
|
||||
|
||||
/**
|
||||
* @brief Time tracker class used internally by the tracker.
|
||||
*/
|
||||
class CTimeTracker
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor.
|
||||
*/
|
||||
CTimeTracker();
|
||||
|
||||
/**
|
||||
* @brief Destructor.
|
||||
*/
|
||||
~CTimeTracker();
|
||||
|
||||
/**
|
||||
* @brief Trigger function to store a trigger timepoint.
|
||||
* @attention Both function and tag name must be a static string in a data segment, so the strings are valid even if the
|
||||
* function that caused the trigger is not within scope any more.
|
||||
* @param[in] szFunction Pointer to the function name.
|
||||
* @param[in] szTag Tag to identify the trigger with.
|
||||
*/
|
||||
void Trigger(const char* szFunction, const char* szTag);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Trigger value structure
|
||||
*/
|
||||
struct STriggerEntry
|
||||
{
|
||||
/**
|
||||
* @brief Constructor of a trigger value.
|
||||
* @param[in] szFuncParam String to function name
|
||||
* @param[in] szTagParam String to tag name
|
||||
*/
|
||||
STriggerEntry(const char* szFuncParam, const char* szTagParam) :
|
||||
szFunction(szFuncParam), szTag(szTagParam), tpTrigger(std::chrono::high_resolution_clock::now())
|
||||
{}
|
||||
|
||||
const char* szFunction; ///< Function name (is a static string in data segment).
|
||||
const char* szTag; ///< Function tag (is a static string in data segment).
|
||||
std::chrono::high_resolution_clock::time_point tpTrigger; ///< Trigger timepoint.
|
||||
};
|
||||
|
||||
std::time_t m_timeStartTime; ///< Start time of the tracker (system clock time).
|
||||
std::chrono::high_resolution_clock::time_point m_tpStartTime; ///< Start time of tracker (high resolution clock).
|
||||
mutable std::mutex m_mtxTriggers; ///< Queue protection.
|
||||
std::queue<STriggerEntry> m_queuTriggers; ///< Queue with trigger values.
|
||||
};
|
||||
|
||||
inline CTimeTracker& GetTimeTracker()
|
||||
{
|
||||
static CTimeTracker tracker_global;
|
||||
return tracker_global;
|
||||
}
|
||||
|
||||
inline CTimeTracker::CTimeTracker() :
|
||||
m_timeStartTime(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())),
|
||||
m_tpStartTime(std::chrono::high_resolution_clock::now())
|
||||
{}
|
||||
|
||||
inline CTimeTracker::~CTimeTracker()
|
||||
{
|
||||
if (m_queuTriggers.empty()) return;
|
||||
|
||||
// Trigger list translated to the system time in microsecs.
|
||||
using TTriggerList = std::list<uint64_t>;
|
||||
struct STagInfo
|
||||
{
|
||||
STagInfo(const char* szFunctionParam, const char* szTagParam) :
|
||||
szFunction(szFunctionParam), szTag(szTagParam)
|
||||
{}
|
||||
const char* szFunction;
|
||||
const char* szTag;
|
||||
TTriggerList lstTriggers;
|
||||
TTriggerList::iterator itPosition;
|
||||
};
|
||||
using TOrderedTagList = std::list<STagInfo>;
|
||||
TOrderedTagList lstOrderedTags;
|
||||
TOrderedTagList lstDeferredTags;
|
||||
|
||||
// Process the trigger queue
|
||||
while (!m_queuTriggers.empty())
|
||||
{
|
||||
// Get the entry
|
||||
STriggerEntry sEntry = std::move(m_queuTriggers.front());
|
||||
m_queuTriggers.pop();
|
||||
|
||||
// Calculate the time since start time (use system clock as reference)
|
||||
uint64_t uiTimeMicrosecs = std::chrono::duration_cast<std::chrono::microseconds>(sEntry.tpTrigger - m_tpStartTime).count();
|
||||
//uiTimeMicrosecs += static_cast<uint64_t>(m_timeStartTime) * 1000000ull;
|
||||
|
||||
// Determine the order
|
||||
STagInfo* psTagInfo = nullptr;
|
||||
if (lstOrderedTags.empty())
|
||||
{
|
||||
// First entry... add to the list
|
||||
lstOrderedTags.push_back(STagInfo(sEntry.szFunction, sEntry.szTag));
|
||||
psTagInfo = &lstOrderedTags.back();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check whether the tag represents the first entry of the order list. If so, add all the deferred list entries.
|
||||
if (sEntry.szTag == lstOrderedTags.front().szTag && !lstDeferredTags.empty())
|
||||
{
|
||||
// Add the deferred tags to the end of the list
|
||||
for (STagInfo& rsTag : lstDeferredTags)
|
||||
lstOrderedTags.push_back(std::move(rsTag));
|
||||
lstDeferredTags.clear();
|
||||
}
|
||||
|
||||
// Search for the tag to exist in the ordered tags list.
|
||||
auto itTag = std::find_if(lstOrderedTags.begin(), lstOrderedTags.end(), [&](const STagInfo& rsTag) { return rsTag.szFunction == sEntry.szFunction && rsTag.szTag == sEntry.szTag; });
|
||||
if (itTag == lstOrderedTags.end())
|
||||
{
|
||||
// Tag is not in the ordered tags list; add to deferred tags list of not already in there
|
||||
itTag = std::find_if(lstDeferredTags.begin(), lstDeferredTags.end(), [&](const STagInfo& rsTag) { return rsTag.szFunction == sEntry.szFunction && rsTag.szTag == sEntry.szTag; });
|
||||
if (itTag == lstDeferredTags.end())
|
||||
{
|
||||
lstDeferredTags.push_back(STagInfo(sEntry.szFunction, sEntry.szTag));
|
||||
psTagInfo = &lstDeferredTags.back();
|
||||
}
|
||||
else
|
||||
psTagInfo = &(*itTag);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tag is in the ordered tags list. Are there any tags on the deferred list? Add them before.
|
||||
for (STagInfo& rsTag : lstDeferredTags)
|
||||
lstOrderedTags.insert(itTag, std::move(rsTag));
|
||||
lstDeferredTags.clear();
|
||||
|
||||
psTagInfo = &(*itTag);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the trigger value
|
||||
if (psTagInfo)
|
||||
psTagInfo->lstTriggers.push_back(uiTimeMicrosecs);
|
||||
else
|
||||
std::cout << "INTERNAL ERROR: tag info pointer is invalid!";
|
||||
}
|
||||
|
||||
// Run through deferred tags lists to add them still at the end of the ordered tags lists
|
||||
for (STagInfo& rsTag : lstDeferredTags)
|
||||
lstOrderedTags.push_back(std::move(rsTag));
|
||||
|
||||
// Create CSV file
|
||||
std::filesystem::path path = (GetExecDirectory() / GetExecFilename().replace_extension("")).generic_u8string() + "_tracker.csv";
|
||||
std::ofstream stream(path);
|
||||
if (!stream.is_open())
|
||||
{
|
||||
std::cout << "Cannot write tracker information to '" << path.generic_u8string() << "'..." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Writing tracker data to '" << path.generic_u8string() << "'..." << std::endl;
|
||||
|
||||
// Iterate through the tags and add the header lines + assign the initial iterator position
|
||||
std::stringstream sstreamFirstLine;
|
||||
std::stringstream sstreamSecondLine;
|
||||
std::stringstream sstreamData;
|
||||
for (STagInfo& rsTagInfo : lstOrderedTags)
|
||||
{
|
||||
// First line is the function name
|
||||
if (!sstreamFirstLine.str().empty())
|
||||
sstreamFirstLine << "; ";
|
||||
sstreamFirstLine << rsTagInfo.szFunction;
|
||||
|
||||
// Second line is the tag
|
||||
if (!sstreamSecondLine.str().empty())
|
||||
sstreamSecondLine << "; ";
|
||||
sstreamSecondLine << rsTagInfo.szTag;
|
||||
|
||||
// Assign iterator
|
||||
rsTagInfo.itPosition = rsTagInfo.lstTriggers.begin();
|
||||
}
|
||||
stream << sstreamFirstLine.str() << std::endl <<
|
||||
sstreamSecondLine.str() << std::endl;
|
||||
|
||||
// Add the data by running though the position iterators so long until all iterators are pointing to end...
|
||||
uint64_t uiMax = 0;
|
||||
do
|
||||
{
|
||||
// Determine the maximum time that includes at the most one value of each tag - check the time of the trigger value
|
||||
// following the current.
|
||||
uiMax = static_cast<uint64_t>(-1);
|
||||
for (const STagInfo& rsTagInfo : lstOrderedTags)
|
||||
{
|
||||
if (rsTagInfo.itPosition != rsTagInfo.lstTriggers.end())
|
||||
{
|
||||
// Get the current timestamp to determine the minimal amount to include this timestamp
|
||||
uint64_t uiPotentialMax = *rsTagInfo.itPosition + 1;
|
||||
auto itNextPosition = rsTagInfo.itPosition;
|
||||
itNextPosition++;
|
||||
|
||||
// If the next timestamp is larger than the current timestamp, take the next timestamp
|
||||
if (itNextPosition != rsTagInfo.lstTriggers.end() && uiPotentialMax < *itNextPosition)
|
||||
uiPotentialMax = *itNextPosition;
|
||||
|
||||
// If the max timestamp should not exceed the next coming timestamp.
|
||||
if (uiMax > uiPotentialMax)
|
||||
uiMax = uiPotentialMax;
|
||||
}
|
||||
}
|
||||
|
||||
// Run through the values and stream those values that are smaller than the next position.
|
||||
bool bFirst = true;
|
||||
for (STagInfo& rsTagInfo : lstOrderedTags)
|
||||
{
|
||||
if (!bFirst)
|
||||
stream << "; ";
|
||||
bFirst = false;
|
||||
if (rsTagInfo.itPosition != rsTagInfo.lstTriggers.end() && *rsTagInfo.itPosition < uiMax)
|
||||
{
|
||||
stream << *rsTagInfo.itPosition;
|
||||
rsTagInfo.itPosition++;
|
||||
}
|
||||
}
|
||||
stream << std::endl;
|
||||
} while (uiMax != static_cast<uint64_t>(-1));
|
||||
|
||||
// Finalize the stream
|
||||
stream.close();
|
||||
}
|
||||
|
||||
inline void CTimeTracker::Trigger(const char* szFunction, const char* szTag)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxTriggers);
|
||||
m_queuTriggers.push(STriggerEntry(szFunction, szTag));
|
||||
}
|
||||
|
||||
#endif // !defined TIME_TRACKER_H
|
||||
77
global/trace.h
Normal file
77
global/trace.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifndef TRACE_H
|
||||
#define TRACE_H
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#ifdef __GNUC__
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_TRACE
|
||||
/**
|
||||
* @brief Enable the trace macro by setting to a non-zero value.
|
||||
*/
|
||||
#define ENABLE_TRACE 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Return the current time as a string.
|
||||
* @return The current time.
|
||||
*/
|
||||
inline std::string GetTimestamp()
|
||||
{
|
||||
const auto current_time_point {std::chrono::system_clock::now()};
|
||||
const auto current_time {std::chrono::system_clock::to_time_t(current_time_point)};
|
||||
const auto current_localtime {*std::localtime (¤t_time)};
|
||||
const auto current_time_since_epoch {current_time_point.time_since_epoch()};
|
||||
const auto current_milliseconds {std::chrono::duration_cast<std::chrono::milliseconds> (current_time_since_epoch).count() % 1000};
|
||||
|
||||
std::ostringstream stream;
|
||||
stream << "PID#" << std::dec << getpid() << " " << std::put_time(¤t_localtime, "%H:%M:%S") << "." << std::setw(3) << std::setfill('0') << current_milliseconds << ": ";
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#if ENABLE_TRACE != 0
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define TRACE(...) Trace(GetTimestamp(), __FUNCTION__, ": ", __VA_ARGS__)
|
||||
#elif defined __GNUC__
|
||||
#define TRACE(...) Trace(GetTimestamp(), __PRETTY_FUNCTION__, ": ", ##__VA_ARGS__)
|
||||
#else
|
||||
#error Other compiler are not supported.
|
||||
#endif
|
||||
|
||||
inline void Trace(std::stringstream& /*rsstream*/)
|
||||
{}
|
||||
|
||||
template <typename TArg, typename... TArgs>
|
||||
inline void Trace(std::stringstream& rsstream, TArg tArg, TArgs... tArgs)
|
||||
{
|
||||
rsstream << tArg;
|
||||
Trace(rsstream, tArgs...);
|
||||
}
|
||||
|
||||
template <typename... TArgs>
|
||||
inline void Trace(TArgs... tArgs)
|
||||
{
|
||||
std::stringstream sstream;
|
||||
Trace(sstream, tArgs...);
|
||||
sstream << std::endl;
|
||||
std::cout << sstream.str();
|
||||
}
|
||||
|
||||
#else // ENABLE_TRACE == 0
|
||||
inline void Trace()
|
||||
{}
|
||||
|
||||
#ifdef __GNUC__
|
||||
#endif
|
||||
#define TRACE(...) Trace()
|
||||
#endif
|
||||
|
||||
#endif // !defined TRACE_H
|
||||
571
global/tracefifo/trace_fifo.cpp
Normal file
571
global/tracefifo/trace_fifo.cpp
Normal file
@@ -0,0 +1,571 @@
|
||||
#include "trace_fifo.h"
|
||||
|
||||
// Include the platform support
|
||||
#define INCLUDE_TRACE_FIFO_PLATFORM
|
||||
#include "trace_fifo_windows.cpp"
|
||||
#include "trace_fifo_posix.cpp"
|
||||
#undef INCLUDE_TRACE_FIFO_PLATFORM
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#pragma push_macro("O_TEXT")
|
||||
#define O_TEXT _O_TEXT
|
||||
#pragma push_macro("dup")
|
||||
#define dup _dup
|
||||
#pragma push_macro("dup2")
|
||||
#define dup2 _dup2
|
||||
#pragma push_macro("fileno")
|
||||
#define fileno _fileno
|
||||
#pragma push_macro("close")
|
||||
#define close _close
|
||||
#pragma push_macro("pipe")
|
||||
#define pipe _pipe
|
||||
#pragma push_macro("read")
|
||||
#define read _read
|
||||
#pragma push_macro("eof")
|
||||
#define eof _eof
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#endif
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
CTraceFifoBase::CTraceFifoBase(uint32_t uiInstanceID, size_t nSize) :
|
||||
m_uiInstanceID(uiInstanceID), m_nDefaultSize(nSize)
|
||||
{}
|
||||
|
||||
CTraceFifoBase::~CTraceFifoBase()
|
||||
{}
|
||||
|
||||
CTraceFifoBase::CTraceFifoBase(CTraceFifoBase&& rfifo) noexcept:
|
||||
m_uiInstanceID(rfifo.m_uiInstanceID), m_nSize(rfifo.m_nSize), m_pBuffer(rfifo.m_pBuffer), m_psHdr(rfifo.m_psHdr)
|
||||
{
|
||||
rfifo.m_pBuffer = nullptr;
|
||||
rfifo.m_psHdr = nullptr;
|
||||
}
|
||||
|
||||
CTraceFifoBase& CTraceFifoBase::operator=(CTraceFifoBase&& rfifo) noexcept
|
||||
{
|
||||
Close();
|
||||
m_uiInstanceID = rfifo.m_uiInstanceID;
|
||||
m_nSize = rfifo.m_nSize;
|
||||
m_pBuffer = rfifo.m_pBuffer;
|
||||
m_psHdr = rfifo.m_psHdr;
|
||||
rfifo.m_pBuffer = nullptr;
|
||||
rfifo.m_psHdr = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CTraceFifoBase::SetInstanceID(uint32_t uiInstanceID)
|
||||
{
|
||||
if (IsOpened()) return false;
|
||||
m_uiInstanceID = uiInstanceID;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t CTraceFifoBase::GetInstanceID() const
|
||||
{
|
||||
return m_uiInstanceID;
|
||||
}
|
||||
|
||||
void CTraceFifoBase::SetDefaultSize(size_t nSize)
|
||||
{
|
||||
m_nDefaultSize = nSize;
|
||||
}
|
||||
|
||||
size_t CTraceFifoBase::GetDefaultSize() const
|
||||
{
|
||||
return m_nDefaultSize;
|
||||
}
|
||||
|
||||
size_t CTraceFifoBase::GetViewSize() const
|
||||
{
|
||||
return m_nSize;
|
||||
}
|
||||
|
||||
size_t CTraceFifoBase::GetDataBufferSize() const
|
||||
{
|
||||
return IsInitialized() ? m_nSize - sizeof(SSharedMemBufHeader) : 0;
|
||||
}
|
||||
|
||||
void CTraceFifoBase::InitializeBuffer(void* pView, size_t nBufferSize, bool bReadOnly)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
|
||||
m_pBuffer = reinterpret_cast<uint8_t*>(pView);
|
||||
m_psHdr = reinterpret_cast<SSharedMemBufHeader*>(pView);
|
||||
m_nSize = nBufferSize;
|
||||
if (!IsInitialized())
|
||||
{
|
||||
if (!bReadOnly)
|
||||
{
|
||||
std::copy_n("SDV_MON\0", 8, m_psHdr->rgszSignature);
|
||||
m_psHdr->uiInstanceID = m_uiInstanceID;
|
||||
m_psHdr->uiTxOffs = 0u;
|
||||
m_bInitConfirmed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CTraceFifoBase::IsInitialized() const
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
|
||||
|
||||
// Bypass of initialization?
|
||||
if (m_bInitConfirmed) return true;
|
||||
|
||||
bool bRet = m_pBuffer && m_psHdr && m_psHdr->uiInstanceID == m_uiInstanceID &&
|
||||
std::equal(m_psHdr->rgszSignature, m_psHdr->rgszSignature + 8, "SDV_MON\0");
|
||||
m_bInitConfirmed = bRet;
|
||||
return bRet;
|
||||
}
|
||||
|
||||
void CTraceFifoBase::Terminate()
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
|
||||
m_pBuffer = nullptr;
|
||||
m_psHdr = nullptr;
|
||||
m_nSize = 0;
|
||||
m_bInitConfirmed = false;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// Static code analysis: not releasing lock in this function is not a failure.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26115)
|
||||
#endif
|
||||
|
||||
std::unique_lock<std::recursive_mutex> CTraceFifoBase::CreateAccessLockObject() const
|
||||
{
|
||||
return std::unique_lock<std::recursive_mutex>(m_mtxAccess);
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
void* CTraceFifoBase::GetView()
|
||||
{
|
||||
return IsInitialized() ? m_pBuffer : 0;
|
||||
}
|
||||
|
||||
uint8_t* CTraceFifoBase::GetDataPtr()
|
||||
{
|
||||
return IsInitialized() ? m_pBuffer + sizeof(SSharedMemBufHeader) : 0;
|
||||
}
|
||||
|
||||
const uint8_t* CTraceFifoBase::GetDataPtr() const
|
||||
{
|
||||
return IsInitialized() ? m_pBuffer + sizeof(SSharedMemBufHeader) : 0;
|
||||
}
|
||||
|
||||
size_t CTraceFifoBase::GetWritePos() const
|
||||
{
|
||||
return IsInitialized() ? m_psHdr->uiTxOffs : 0;
|
||||
}
|
||||
|
||||
void CTraceFifoBase::SetWritePos(size_t nTxPos)
|
||||
{
|
||||
if (IsInitialized() && nTxPos < GetDataBufferSize())
|
||||
m_psHdr->uiTxOffs = static_cast<uint32_t>(nTxPos);
|
||||
}
|
||||
|
||||
CTraceFifoReader::CTraceFifoReader(uint32_t uiInstanceID /*= 1000u*/, size_t nSize /*= 16384*/) :
|
||||
CTraceFifoImpl(uiInstanceID, nSize)
|
||||
{}
|
||||
|
||||
CTraceFifoReader::~CTraceFifoReader()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
CTraceFifoReader::CTraceFifoReader(CTraceFifoReader&& rfifo) noexcept : CTraceFifoImpl(static_cast<CTraceFifoImpl&&>(rfifo)), m_nRxOffs(rfifo.m_nRxOffs)
|
||||
{
|
||||
rfifo.m_nRxOffs = 0;
|
||||
}
|
||||
|
||||
CTraceFifoReader& CTraceFifoReader::operator=(CTraceFifoReader&& rfifo) noexcept
|
||||
{
|
||||
CTraceFifoImpl::operator=(static_cast<CTraceFifoImpl&&>(rfifo));
|
||||
rfifo.m_nRxOffs = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CTraceFifoReader::Open(size_t nTimeout /*= 1000*/, uint32_t uiFlags /*= 0u*/)
|
||||
{
|
||||
if (IsOpened()) return true;
|
||||
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
|
||||
bool bRet = CTraceFifoImpl::Open(nTimeout, uiFlags | static_cast<uint32_t>(ETraceFifoOpenFlags::read_only));
|
||||
if (bRet) m_nRxOffs = GetWritePos();
|
||||
return bRet && IsOpened();
|
||||
}
|
||||
|
||||
inline void CTraceFifoReader::Close()
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
|
||||
CTraceFifoImpl::Close();
|
||||
Terminate();
|
||||
}
|
||||
|
||||
std::string CTraceFifoReader::WaitForMessage(size_t nTimeout /*= 1000*/)
|
||||
{
|
||||
if (!IsOpened())
|
||||
{
|
||||
Open(nTimeout);
|
||||
if (!IsOpened()) return {}; // Must be connected
|
||||
}
|
||||
|
||||
auto tpStart = std::chrono::high_resolution_clock::now();
|
||||
do
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
|
||||
if (!IsInitialized()) break;
|
||||
|
||||
// Count the characters in the string, not including the null-character. Returns nMaxLen when no null-character has been
|
||||
// found.
|
||||
auto fnCountStr = [](const uint8_t* szStr, size_t nMaxLen)
|
||||
{
|
||||
for (size_t nCount = 0; nCount < nMaxLen; nCount++)
|
||||
if (!szStr[nCount]) return nCount;
|
||||
return nMaxLen;
|
||||
};
|
||||
|
||||
// If Rx is higher than Tx, read at the most until the end of the buffer and the rest from the start. If the Tx is smaller
|
||||
// than Tx, read at the most until Tx.
|
||||
size_t nBuffSize = GetDataBufferSize();
|
||||
uint8_t* pBuffer = GetDataPtr();
|
||||
size_t nLocalRx = m_nRxOffs;
|
||||
std::string ssMsg;
|
||||
if (nLocalRx > GetWritePos())
|
||||
{
|
||||
// Detect the length
|
||||
size_t uiMaxLen1 = nBuffSize - m_nRxOffs;
|
||||
size_t nLen1 = fnCountStr(pBuffer + m_nRxOffs, uiMaxLen1);
|
||||
size_t nMaxLen2 = GetWritePos();
|
||||
size_t nLen2 = nLen1 == uiMaxLen1 ? fnCountStr(pBuffer, nMaxLen2) : 0;
|
||||
if (nLen1 || nLen2 != nMaxLen2)
|
||||
{
|
||||
ssMsg.resize(nLen1 + nLen2);
|
||||
if (nLen1)
|
||||
std::copy(pBuffer + m_nRxOffs, pBuffer + m_nRxOffs + nLen1, ssMsg.begin());
|
||||
if (nLen2)
|
||||
std::copy(pBuffer, pBuffer + nLen2, ssMsg.begin() + nLen1);
|
||||
|
||||
// Update Rx
|
||||
m_nRxOffs = nLen2 ? nLen2 + 1 : (nLen1 == uiMaxLen1 ? 1 : m_nRxOffs + nLen1 + 1);
|
||||
|
||||
// Return result
|
||||
return ssMsg;
|
||||
}
|
||||
}
|
||||
else if (m_nRxOffs < GetWritePos())
|
||||
{
|
||||
// Copy the string
|
||||
size_t nMaxLen = GetWritePos() - m_nRxOffs;
|
||||
size_t nLen = fnCountStr(pBuffer + m_nRxOffs, nMaxLen);
|
||||
if (nLen != nMaxLen)
|
||||
{
|
||||
// Copy characters and include null-character.
|
||||
ssMsg.resize(nLen);
|
||||
std::copy(pBuffer + m_nRxOffs, pBuffer + m_nRxOffs + nLen, ssMsg.begin());
|
||||
|
||||
// Update Rx
|
||||
m_nRxOffs += nLen + 1;
|
||||
|
||||
// Return result
|
||||
return ssMsg;
|
||||
}
|
||||
}
|
||||
else if (!nTimeout)
|
||||
return {};
|
||||
else
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
} while (std::chrono::duration_cast<std::chrono::duration<size_t, std::milli>>(
|
||||
std::chrono::high_resolution_clock::now() - tpStart).count() < nTimeout);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
CTraceFifoWriter::CTraceFifoWriter(uint32_t uiInstanceID /*= 1000u*/, size_t nSize /*= 16384*/) :
|
||||
CTraceFifoImpl(uiInstanceID, nSize)
|
||||
{}
|
||||
|
||||
CTraceFifoWriter::~CTraceFifoWriter()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
CTraceFifoWriter::CTraceFifoWriter(CTraceFifoWriter&& rfifo) noexcept : CTraceFifoImpl(static_cast<CTraceFifoImpl&&>(rfifo))
|
||||
{}
|
||||
|
||||
CTraceFifoWriter& CTraceFifoWriter::operator=(CTraceFifoWriter&& rfifo) noexcept
|
||||
{
|
||||
CTraceFifoImpl::operator=(static_cast<CTraceFifoImpl&&>(rfifo));
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CTraceFifoWriter::Open(size_t nTimeout /*= 1000*/, uint32_t uiFlags /*= 0u*/)
|
||||
{
|
||||
if (IsOpened()) return true;
|
||||
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
|
||||
return CTraceFifoImpl::Open(nTimeout, uiFlags & ~static_cast<uint32_t>(ETraceFifoOpenFlags::read_only));
|
||||
}
|
||||
|
||||
void CTraceFifoWriter::Close()
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
|
||||
CTraceFifoImpl::Close();
|
||||
Terminate();
|
||||
}
|
||||
|
||||
void CTraceFifoWriter::Publish(const std::string& rssMessage)
|
||||
{
|
||||
if (!IsOpened())
|
||||
{
|
||||
Open();
|
||||
if (!IsOpened()) return; // Must be connected
|
||||
}
|
||||
|
||||
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
|
||||
|
||||
// Message size is limited
|
||||
size_t nBuffSize = GetDataBufferSize();
|
||||
uint8_t* pBuffer = GetDataPtr();
|
||||
if (rssMessage.size() + 2u > nBuffSize) return;
|
||||
|
||||
// Two chunks to copy:
|
||||
// 1. From start of message to end of message or end of buffer (whatever is lower).
|
||||
// 2. When leftover, from start of buffer until end of message.
|
||||
size_t nStart = GetWritePos();
|
||||
size_t nLen = rssMessage.size() + 1u;
|
||||
size_t nStop = nStart + nLen;
|
||||
size_t nLen1 = std::min(nBuffSize - nStart, nLen);
|
||||
size_t nLen2 = nLen - nLen1;
|
||||
|
||||
// Copy the message
|
||||
if (nLen1) std::copy_n(rssMessage.c_str(), nLen1, pBuffer + GetWritePos());
|
||||
if (nLen2) std::copy_n(rssMessage.c_str() + nLen1, nLen2, pBuffer);
|
||||
SetWritePos(nStop > nBuffSize ? nLen2 : nStop);
|
||||
}
|
||||
|
||||
CTraceFifoStreamBuffer::CTraceFifoStreamBuffer(uint32_t uiInstanceID /*= 1000u*/, size_t nSize /*= 16384*/) :
|
||||
CTraceFifoWriter(uiInstanceID, nSize)
|
||||
{}
|
||||
|
||||
CTraceFifoStreamBuffer::~CTraceFifoStreamBuffer()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
void CTraceFifoStreamBuffer::InterceptStream(std::ostream& rstream)
|
||||
{
|
||||
m_mapBindings.emplace(&rstream, std::make_unique<SInterceptBinding>(rstream, *this));
|
||||
}
|
||||
|
||||
void CTraceFifoStreamBuffer::RevertInterception(std::ostream& rstream)
|
||||
{
|
||||
auto itBinding = m_mapBindings.find(&rstream);
|
||||
if (itBinding != m_mapBindings.end())
|
||||
m_mapBindings.erase(itBinding);
|
||||
}
|
||||
|
||||
void CTraceFifoStreamBuffer::Close()
|
||||
{
|
||||
sync();
|
||||
m_mapBindings.clear();
|
||||
CTraceFifoWriter::Close();
|
||||
}
|
||||
|
||||
int CTraceFifoStreamBuffer::sync()
|
||||
{
|
||||
Publish(str());
|
||||
if (!m_mapBindings.empty())
|
||||
m_mapBindings.begin()->second->streamOrginal << str();
|
||||
str(std::string()); // Clear the string buffer
|
||||
return 0;
|
||||
}
|
||||
|
||||
CTraceFifoStreamBuffer::SInterceptBinding::SInterceptBinding(std::ostream& rstream, CTraceFifoStreamBuffer& rstreamBuffer) :
|
||||
rstreamIntercepted(rstream), streamOrginal(rstream.rdbuf(&rstreamBuffer))
|
||||
{}
|
||||
|
||||
CTraceFifoStreamBuffer::SInterceptBinding::~SInterceptBinding()
|
||||
{
|
||||
rstreamIntercepted.rdbuf(streamOrginal.rdbuf(nullptr));
|
||||
}
|
||||
|
||||
CTraceFifoStdBuffer::CTraceFifoStdBuffer(uint32_t uiInstanceID /*= 1000u*/, size_t nSize /*= 16384*/) :
|
||||
CTraceFifoWriter(uiInstanceID, nSize)
|
||||
{}
|
||||
|
||||
CTraceFifoStdBuffer::~CTraceFifoStdBuffer()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
bool CTraceFifoStdBuffer::Open(size_t nTimeout /*= 1000*/, uint32_t uiFlags /*= 0u*/)
|
||||
{
|
||||
// Close before...
|
||||
Close();
|
||||
|
||||
// Open the fifo
|
||||
bool bRet = CTraceFifoWriter::Open(nTimeout, uiFlags);
|
||||
if (!bRet)
|
||||
{
|
||||
std::cout << "Help -1" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create two pipes for the standard streams stdout and stderr.
|
||||
#ifdef _WIN32
|
||||
bRet = pipe(m_rgPipeStdOut, 16384, O_TEXT) == 0;
|
||||
if (bRet) bRet = pipe(m_rgPipeStdErr, 16384, O_TEXT) == 0;
|
||||
#else
|
||||
bRet = pipe(m_rgPipeStdOut) == 0;
|
||||
if (bRet) bRet = pipe(m_rgPipeStdErr) == 0;
|
||||
#endif
|
||||
|
||||
// Duplicate the StdOut and StdErr descriptors
|
||||
if (bRet)
|
||||
{
|
||||
m_iOldStdOut = dup(fileno(stdout));
|
||||
m_iOldStdErr = dup(fileno(stderr));
|
||||
if (m_iOldStdOut == -1 || m_iOldStdErr == -1)
|
||||
bRet = false;
|
||||
}
|
||||
|
||||
// Assign the pipes to the StdOut and StdErr
|
||||
if (bRet) bRet = dup2(m_rgPipeStdOut[nWriteIndex], fileno(stdout)) >= 0;
|
||||
if (bRet) bRet = dup2(m_rgPipeStdErr[nWriteIndex], fileno(stderr)) >= 0;
|
||||
|
||||
// Start the dispatch thread
|
||||
m_bShutdown = false;
|
||||
if (bRet) m_threadDispatch = std::thread(&CTraceFifoStdBuffer::DispatchThreadFunc, this);
|
||||
|
||||
if (!bRet)
|
||||
{
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// Prevent static code analysis warning for unused return value of _dup2.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 6031)
|
||||
#endif
|
||||
|
||||
void CTraceFifoStdBuffer::Close()
|
||||
{
|
||||
// Prevent multiple re-entries
|
||||
if (!m_bShutdown && (m_iOldStdOut != -1 || m_iOldStdErr != -1))
|
||||
{
|
||||
// Flush all streams
|
||||
std::cout.flush();
|
||||
std::clog.flush();
|
||||
std::cerr.flush();
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
|
||||
// Wait for processing finishing the dispatching
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
|
||||
// Reassign the existing descriptors of StdOut and StdErr.
|
||||
if (m_iOldStdOut != -1)
|
||||
dup2(m_iOldStdOut, fileno(stdout));
|
||||
if (m_iOldStdErr != -1)
|
||||
dup2(m_iOldStdErr, fileno(stderr));
|
||||
|
||||
// Shutdown the thread
|
||||
m_bShutdown = true;
|
||||
if (m_threadDispatch.joinable())
|
||||
m_threadDispatch.join();
|
||||
}
|
||||
|
||||
// Close the duplictaed desciptors
|
||||
if (m_iOldStdOut != -1)
|
||||
close(m_iOldStdOut);
|
||||
if (m_iOldStdErr != -1)
|
||||
close(m_iOldStdErr);
|
||||
if (m_rgPipeStdOut[nReadIndex] != -1)
|
||||
close(m_rgPipeStdOut[nReadIndex]);
|
||||
if (m_rgPipeStdOut[nWriteIndex] != -1)
|
||||
close(m_rgPipeStdOut[nWriteIndex]);
|
||||
if (m_rgPipeStdErr[nReadIndex] != -1)
|
||||
close(m_rgPipeStdErr[nReadIndex]);
|
||||
if (m_rgPipeStdErr[nWriteIndex] != -1)
|
||||
close(m_rgPipeStdErr[nWriteIndex]);
|
||||
m_iOldStdOut = -1;
|
||||
m_iOldStdErr = -1;
|
||||
m_rgPipeStdOut[nReadIndex] = -1;
|
||||
m_rgPipeStdOut[nWriteIndex] = -1;
|
||||
m_rgPipeStdErr[nReadIndex] = -1;
|
||||
m_rgPipeStdErr[nWriteIndex] = -1;
|
||||
|
||||
// Close the fifo
|
||||
CTraceFifoWriter::Close();
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
void CTraceFifoStdBuffer::DispatchThreadFunc()
|
||||
{
|
||||
sdv::pointer<char> ptrBuffer;
|
||||
ptrBuffer.resize(8192);
|
||||
while (!m_bShutdown)
|
||||
{
|
||||
// Read the StdOut pipe
|
||||
while (!m_bShutdown && m_rgPipeStdOut[nReadIndex] != -1)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (eof(m_rgPipeStdOut[nReadIndex])) break;
|
||||
#else
|
||||
struct pollfd sPoll{};
|
||||
sPoll.fd = m_rgPipeStdOut[nReadIndex];
|
||||
sPoll.events = POLLIN;
|
||||
if (poll(&sPoll, 1, 1) <= 0) break;
|
||||
#endif
|
||||
int iBytesRead = read(m_rgPipeStdOut[nReadIndex], ptrBuffer.get(), static_cast<unsigned int>(ptrBuffer.size()));
|
||||
if (iBytesRead <= 0) break;
|
||||
std::string ssMsg(ptrBuffer.get(), static_cast<size_t>(iBytesRead));
|
||||
Publish(ssMsg);
|
||||
}
|
||||
|
||||
// Read the StdErr pipe
|
||||
while (!m_bShutdown && m_rgPipeStdErr[nReadIndex] != -1)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (eof(m_rgPipeStdErr[nReadIndex])) break;
|
||||
#else
|
||||
struct pollfd sPoll{};
|
||||
sPoll.fd = m_rgPipeStdErr[nReadIndex];
|
||||
sPoll.events = POLLIN;
|
||||
if (poll(&sPoll, 1, 1) <= 0) break;
|
||||
#endif
|
||||
int iBytesRead = read(m_rgPipeStdErr[nReadIndex], ptrBuffer.get(), static_cast<unsigned int>(ptrBuffer.size()));
|
||||
if (iBytesRead <= 0) break;
|
||||
std::string ssMsg(ptrBuffer.get(), static_cast<size_t>(iBytesRead));
|
||||
Publish(ssMsg);
|
||||
}
|
||||
|
||||
// Wait 10 ms
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma pop_macro("O_TEXT")
|
||||
#pragma pop_macro("dup")
|
||||
#pragma pop_macro("dup2")
|
||||
#pragma pop_macro("fileno")
|
||||
#pragma pop_macro("close")
|
||||
#pragma pop_macro("pipe")
|
||||
#pragma pop_macro("read")
|
||||
#pragma pop_macro("eof")
|
||||
#endif
|
||||
473
global/tracefifo/trace_fifo.h
Normal file
473
global/tracefifo/trace_fifo.h
Normal file
@@ -0,0 +1,473 @@
|
||||
#ifndef TRACE_FIFO_H
|
||||
#define TRACE_FIFO_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
|
||||
/**
|
||||
* @brief trace fifo open flags
|
||||
*/
|
||||
enum class ETraceFifoOpenFlags : uint32_t
|
||||
{
|
||||
open_only = 1, ///< Open only. Do not create a new shared memory area.
|
||||
force_create = 2, ///< Create a new shared memory area, removing any previous connection.
|
||||
read_only = 4, ///< Open the shared memory for read-only access.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Trace fifo class allowing the publishing and monitoring of trace messages.
|
||||
*/
|
||||
class CTraceFifoBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
|
||||
* @param[in] nSize Default size of the fifo.
|
||||
*/
|
||||
CTraceFifoBase(uint32_t uiInstanceID, size_t nSize);
|
||||
|
||||
/**
|
||||
* @brief Default destructor
|
||||
* @remarks Automatically closes the fifo if opened before.
|
||||
*/
|
||||
virtual ~CTraceFifoBase();
|
||||
|
||||
/**
|
||||
* @brief Copy constructor is not available.
|
||||
* @param[in] rfifo Reference to the fifo to copy.
|
||||
*/
|
||||
CTraceFifoBase(const CTraceFifoBase& rfifo) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
*/
|
||||
CTraceFifoBase(CTraceFifoBase&& rfifo) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Copy assignment is not available.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
* @return Returns a reference to this fifo.
|
||||
*/
|
||||
CTraceFifoBase& operator=(const CTraceFifoBase& rfifo) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
* @return Returns a reference to this fifo.
|
||||
*/
|
||||
CTraceFifoBase& operator=(CTraceFifoBase&& rfifo) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Open the fifo. Implemented by derived class.
|
||||
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
|
||||
* established.
|
||||
* @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
|
||||
* @return Returns true when connected; false otherwise.
|
||||
*/
|
||||
virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) = 0;
|
||||
|
||||
/**
|
||||
* @brief Cancel any running task and close an open fifo. Implemented by derived class.
|
||||
*/
|
||||
virtual void Close() = 0;
|
||||
|
||||
/**
|
||||
* @brief Is the fifo open for reading and writing? Implemented by derived class.
|
||||
* @return Returns true when opened; false otherwise.
|
||||
*/
|
||||
virtual bool IsOpened() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Set a new instance ID for the communication.
|
||||
* @remarks The instance ID can only be set when the fifo is not opened yet.
|
||||
* @param[in] uiInstanceID The new instance ID.
|
||||
* @return Returns whether setting the instance ID was successful.
|
||||
*/
|
||||
bool SetInstanceID(uint32_t uiInstanceID);
|
||||
|
||||
/**
|
||||
* @brief Return the instance ID to use for the communication.
|
||||
* @return The instance ID.
|
||||
*/
|
||||
uint32_t GetInstanceID() const;
|
||||
|
||||
/**
|
||||
* @brief Set a new default size to be used during creation of the fifo.
|
||||
* @param[in] nSize The new default size of the buffer.
|
||||
*/
|
||||
void SetDefaultSize(size_t nSize);
|
||||
|
||||
/**
|
||||
* @brief Get the default size used during creation of the fifo.
|
||||
* @return The default size of the buffer.
|
||||
*/
|
||||
size_t GetDefaultSize() const;
|
||||
|
||||
/**
|
||||
* @brief Get the size of the view.
|
||||
* @return The view size.
|
||||
*/
|
||||
size_t GetViewSize() const;
|
||||
|
||||
/**
|
||||
* @brief Get the size of the buffer without any headers.
|
||||
* @return Returns the size of the buffer or 0 when the buffer is not initialized.
|
||||
*/
|
||||
size_t GetDataBufferSize() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Initialize the buffer after successful opening.
|
||||
* @param[in] pView Pointer to the shared memory view.
|
||||
* @param[in] nBufferSize The size of the buffer allocated.
|
||||
* @param[in] bReadOnly When set, the buffer is initialized as read-only. Multiple read-only and only one writable buffer access
|
||||
* are allowed.
|
||||
*/
|
||||
void InitializeBuffer(void* pView, size_t nBufferSize, bool bReadOnly);
|
||||
|
||||
/**
|
||||
* @brief Check whether the buffer is initialized (signature and instance ID are set).
|
||||
* @return Returns true when the buffer has been initialized; false when not.
|
||||
*/
|
||||
bool IsInitialized() const;
|
||||
|
||||
/**
|
||||
* @brief Clear the buffer pointers
|
||||
*/
|
||||
void Terminate();
|
||||
|
||||
/**
|
||||
* @brief Create a lock object for exclusive access of the buffer.
|
||||
* @return The lock object that allows access.
|
||||
*/
|
||||
std::unique_lock<std::recursive_mutex> CreateAccessLockObject() const;
|
||||
|
||||
/**
|
||||
* @brief Get access to the view.
|
||||
* @return Get a pointer to the buffer. Returns NULL when the buffer is not initialized.
|
||||
*/
|
||||
void* GetView();
|
||||
|
||||
/**
|
||||
* @{
|
||||
* @brief Get access to the data portion of the buffer.
|
||||
* @return Get a pointer to the data. Returns NULL when the buffer is not initialized.
|
||||
*/
|
||||
uint8_t* GetDataPtr();
|
||||
const uint8_t* GetDataPtr() const;
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Get the write position.
|
||||
* @return The current write position within the buffer.
|
||||
*/
|
||||
size_t GetWritePos() const;
|
||||
|
||||
/**
|
||||
* @brief Set the write position. Can only be used by writable buffer (causing an access violation otherwise).
|
||||
* @param[in] nTxPos The new write position.
|
||||
*/
|
||||
void SetWritePos(size_t nTxPos);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Shared memory header structure; at the front of the shared memory buffer.
|
||||
*/
|
||||
struct SSharedMemBufHeader
|
||||
{
|
||||
char rgszSignature[8]; ///< Signature "SDV_MON\0"
|
||||
uint32_t uiInstanceID; ///< Instance ID of the server instance
|
||||
volatile uint32_t uiTxOffs; ///< Tx position
|
||||
};
|
||||
|
||||
mutable volatile bool m_bInitConfirmed = false; ///< When set, bypasses the header checking.
|
||||
uint32_t m_uiInstanceID = 1000; ///< Instance ID to use while connecting.
|
||||
size_t m_nSize = 0; ///< Size of the fifo.
|
||||
size_t m_nDefaultSize = 0; ///< Requested size.
|
||||
uint8_t* m_pBuffer = nullptr; ///< Pointer to the buffer.
|
||||
SSharedMemBufHeader* m_psHdr = nullptr; ///< Shared memory header at from of the buffer.
|
||||
mutable std::recursive_mutex m_mtxAccess; ///< Protect against sudden closure.
|
||||
};
|
||||
|
||||
// Include the implementation classes classes for the trace fifo
|
||||
#include "trace_fifo_windows.h"
|
||||
#include "trace_fifo_posix.h"
|
||||
|
||||
/**
|
||||
* @brief Reader class for the trace fifo. Multiple readers can coexist.
|
||||
*/
|
||||
class CTraceFifoReader : public CTraceFifoImpl
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
|
||||
* @param[in] nSize Default size of the fifo.
|
||||
*/
|
||||
CTraceFifoReader(uint32_t uiInstanceID = 1000u, size_t nSize = 16384);
|
||||
|
||||
/**
|
||||
* @brief Default destructor
|
||||
* @remarks Automatically closes the fifo if opened before.
|
||||
*/
|
||||
virtual ~CTraceFifoReader() override;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor is not available.
|
||||
* @param[in] rfifo Reference to the fifo to copy.
|
||||
*/
|
||||
CTraceFifoReader(const CTraceFifoReader& rfifo) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
*/
|
||||
CTraceFifoReader(CTraceFifoReader&& rfifo) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Copy assignment is not available.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
* @return Returns a reference to this fifo.
|
||||
*/
|
||||
CTraceFifoReader& operator=(const CTraceFifoReader& rfifo) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
* @return Returns a reference to this fifo.
|
||||
*/
|
||||
CTraceFifoReader& operator=(CTraceFifoReader&& rfifo) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Open the fifo. Override of CTraceFifoBase::Open.
|
||||
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
|
||||
* established.
|
||||
* @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
|
||||
* @return Returns true when connected; false otherwise.
|
||||
*/
|
||||
virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;
|
||||
|
||||
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
|
||||
// cppcheck-suppress virtualCallInConstructor
|
||||
/**
|
||||
* @brief Cancel any running task and close an open fifo. Override of CTraceFifoBase::Close.
|
||||
*/
|
||||
virtual void Close() override;
|
||||
|
||||
/**
|
||||
* @brief Wait for a message.
|
||||
* @remarks Automatically opens the fifo if not opened before.
|
||||
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a message has been
|
||||
* received.
|
||||
* @return The received message or an empty message if a timeout occurred or the publisher had sent an empty message.
|
||||
*/
|
||||
std::string WaitForMessage(size_t nTimeout = 1000);
|
||||
|
||||
private:
|
||||
size_t m_nRxOffs = 0; ///< Reader position
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Writer class for a trace fifo. Only one writer should be present.
|
||||
*/
|
||||
class CTraceFifoWriter : public CTraceFifoImpl
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
|
||||
* @param[in] nSize Default size of the fifo.
|
||||
*/
|
||||
CTraceFifoWriter(uint32_t uiInstanceID = 1000u, size_t nSize = 16384);
|
||||
|
||||
/**
|
||||
* @brief Default destructor
|
||||
* @remarks Automatically closes the fifo if opened before.
|
||||
*/
|
||||
virtual ~CTraceFifoWriter() override;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor is not available.
|
||||
* @param[in] rfifo Reference to the fifo to copy.
|
||||
*/
|
||||
CTraceFifoWriter(const CTraceFifoWriter& rfifo) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
*/
|
||||
CTraceFifoWriter(CTraceFifoWriter&& rfifo) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Copy assignment is not available.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
* @return Returns a reference to this fifo.
|
||||
*/
|
||||
CTraceFifoWriter& operator=(const CTraceFifoWriter& rfifo) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
* @return Returns a reference to this fifo.
|
||||
*/
|
||||
CTraceFifoWriter& operator=(CTraceFifoWriter&& rfifo) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Open the fifo. Override of CTraceFifoBase::Open.
|
||||
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
|
||||
* established.
|
||||
* @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
|
||||
* @return Returns true when connected; false otherwise.
|
||||
*/
|
||||
virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;
|
||||
|
||||
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
|
||||
// cppcheck-suppress virtualCallInConstructor
|
||||
/**
|
||||
* @brief Cancel any running task and close an open fifo. Override of CTraceFifoBase::Close.
|
||||
*/
|
||||
virtual void Close() override;
|
||||
|
||||
/**
|
||||
* @brief Publish a message. If the buffer is full, the oldest message is removed.
|
||||
* @remarks Automatically opens the fifo if not opened before.
|
||||
* @param[in] rssMessage Reference to the message to publish.
|
||||
*/
|
||||
void Publish(const std::string& rssMessage);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Trace fifo stream buffer object.
|
||||
* @remarks This implementation works for applications within the scope of the C++ library source compilation into one binary unit.
|
||||
* This is due to the templated nature of the C++ library allowing very little reuse of compiled code across binary units. This
|
||||
* means, that although interception will work in one unit (e.g. executable or static/shared library), it might not intercept
|
||||
* messages from another unit.
|
||||
*/
|
||||
class CTraceFifoStreamBuffer : public CTraceFifoWriter, public std::stringbuf
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
|
||||
* @param[in] nSize Default size of the fifo.
|
||||
*/
|
||||
CTraceFifoStreamBuffer(uint32_t uiInstanceID = 1000u, size_t nSize = 16384);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
virtual ~CTraceFifoStreamBuffer() override;
|
||||
|
||||
/**
|
||||
* @brief Assign this buffer to a stream object. Any message will be streamed to both the original stream as well as the trace
|
||||
* fifo.
|
||||
* @attention The stream object needs to stay in scope until the assignment is removed or this class is destroyed.
|
||||
* @attention Works for text only.
|
||||
* @remarks Removal of this assignment is done automatically during destruction of this class.
|
||||
* @param[in] rstream Reference to the stream to intercept the communication for.
|
||||
*/
|
||||
void InterceptStream(std::ostream& rstream);
|
||||
|
||||
/**
|
||||
* @brief Remove the interception of a stream.
|
||||
* @param[in] rstream Reference to the stream to revert the interception for.
|
||||
*/
|
||||
void RevertInterception(std::ostream& rstream);
|
||||
|
||||
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
|
||||
// cppcheck-suppress virtualCallInConstructor
|
||||
/**
|
||||
* @brief Cancel any running task and close an open fifo. Override of CTraceFifoBase::Close.
|
||||
*/
|
||||
virtual void Close() override;
|
||||
|
||||
/**
|
||||
* @brief Synchronizes the buffers with the associated character sequence.
|
||||
* @return Returns 0 if successful; -1 if not.
|
||||
*/
|
||||
int sync();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Stream interception binding.
|
||||
*/
|
||||
struct SInterceptBinding
|
||||
{
|
||||
/**
|
||||
* @brief Constructor doing the binding.
|
||||
*/
|
||||
SInterceptBinding(std::ostream& rstream, CTraceFifoStreamBuffer& rstreamBuffer);
|
||||
|
||||
//SInterceptBinding(SInterceptBinding& rsBinding) : rstreamIntercepted(rsBinding.rstreamIntercepted), streamOrginal(rsBinding.streamOrginal.rdbuf()) {}
|
||||
|
||||
/**
|
||||
* @brief Destructor undoing the binding.
|
||||
*/
|
||||
~SInterceptBinding();
|
||||
|
||||
std::ostream& rstreamIntercepted; ///< The intercepted stream
|
||||
std::ostream streamOrginal; ///< Stream object redirecting the buffer.
|
||||
};
|
||||
|
||||
std::map<std::ostream*, std::unique_ptr<SInterceptBinding>> m_mapBindings; ///< Map with intercepted stream bindings.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Interception and dispatching of all messages from STDOUT and STDERR to the trace fifo.
|
||||
*/
|
||||
class CTraceFifoStdBuffer : public CTraceFifoWriter
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
|
||||
* @param[in] nSize Default size of the fifo.
|
||||
*/
|
||||
CTraceFifoStdBuffer(uint32_t uiInstanceID = 1000u, size_t nSize = 16384);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
virtual ~CTraceFifoStdBuffer() override;
|
||||
|
||||
/**
|
||||
* @brief Open the fifo and direct all messages from StdOut and StdErr to the trace fifo. Override of CTraceFifoBase::Open.
|
||||
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
|
||||
* established.
|
||||
* @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
|
||||
* @return Returns true when connected; false otherwise.
|
||||
*/
|
||||
virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;
|
||||
|
||||
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
|
||||
// cppcheck-suppress virtualCallInConstructor
|
||||
/**
|
||||
* @brief Revert the redirection and close the open fifo. Override of CTraceFifoBase::Close.
|
||||
*/
|
||||
virtual void Close() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Read from the pipe and forward the text to the trace fifo. Read until shutdown flag is switch on.
|
||||
*/
|
||||
void DispatchThreadFunc();
|
||||
|
||||
const size_t nReadIndex = 0; ///< The read index of the pipe descriptor.
|
||||
const size_t nWriteIndex = 1; ///< The write index of the pipe descriptor.
|
||||
bool m_bShutdown = false; ///< Shutdown flag for the pipe reader thread.
|
||||
int m_rgPipeStdOut[2] = { -1, -1 }; ///< StdOut pipe with read and write descriptors.
|
||||
int m_rgPipeStdErr[2] = { -1, -1 }; ///< StdErr pipe with read and write descriptors.
|
||||
int m_iOldStdOut = -1; ///< Old descriptor for the StdOut
|
||||
int m_iOldStdErr = -1; ///< Old descriptor for the StdErr
|
||||
std::thread m_threadDispatch; ///< Dispatch thread
|
||||
};
|
||||
|
||||
#endif // !defined TRACE_FIFO_H
|
||||
148
global/tracefifo/trace_fifo_posix.cpp
Normal file
148
global/tracefifo/trace_fifo_posix.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
#if defined __unix__
|
||||
|
||||
#ifndef INCLUDE_TRACE_FIFO_PLATFORM
|
||||
#error Do not include this file directly. The file is included by trace_fifo.cpp.
|
||||
#endif
|
||||
|
||||
#include "trace_fifo_posix.h"
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <semaphore.h>
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <support/string.h>
|
||||
|
||||
CTraceFifoPosix::CTraceFifoPosix(uint32_t uiInstanceID /*= 1000u*/, size_t nSize /*= 16*1024*/) :
|
||||
CTraceFifoBase(uiInstanceID, nSize)
|
||||
{}
|
||||
|
||||
CTraceFifoPosix::~CTraceFifoPosix()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
CTraceFifoPosix::CTraceFifoPosix(CTraceFifoPosix&& rfifo) :
|
||||
CTraceFifoBase(static_cast<CTraceFifoBase&&>(rfifo)), m_iFileDescr(rfifo.m_iFileDescr)
|
||||
{
|
||||
rfifo.m_iFileDescr = 0;
|
||||
}
|
||||
|
||||
CTraceFifoPosix& CTraceFifoPosix::operator=(CTraceFifoPosix&& rfifo)
|
||||
{
|
||||
Close();
|
||||
CTraceFifoBase::operator=(static_cast<CTraceFifoBase&&>(rfifo));
|
||||
m_iFileDescr = rfifo.m_iFileDescr;
|
||||
rfifo.m_iFileDescr = 0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CTraceFifoPosix::Open(size_t nTimeout /*= 1000*/, uint32_t uiFlags /*= 0u*/)
|
||||
{
|
||||
std::string ssSharedMemName = "SDV_LOG_MONITOR_" + std::to_string(GetInstanceID());
|
||||
|
||||
auto lock = CreateAccessLockObject();
|
||||
|
||||
auto tpStart = std::chrono::high_resolution_clock::now();
|
||||
bool bOpenOnly = uiFlags & static_cast<uint32_t>(ETraceFifoOpenFlags::open_only);
|
||||
bool bForceCreate = uiFlags & static_cast<uint32_t>(ETraceFifoOpenFlags::force_create);
|
||||
bool bReadOnly = uiFlags & static_cast<uint32_t>(ETraceFifoOpenFlags::read_only);
|
||||
if (bOpenOnly && bForceCreate) return false;
|
||||
|
||||
// In case of a force-create-flag, unlink a potential previous allocation.
|
||||
if (bForceCreate) shm_unlink(ssSharedMemName.c_str());
|
||||
|
||||
do
|
||||
{
|
||||
if (IsOpened()) break;
|
||||
|
||||
// Try creating the file mapping object
|
||||
if (!m_iFileDescr)
|
||||
{
|
||||
int iMapAccess = O_RDWR | O_CREAT;
|
||||
if (bOpenOnly)
|
||||
iMapAccess = bReadOnly ? O_RDONLY : O_RDWR;
|
||||
else if (bForceCreate)
|
||||
iMapAccess |= O_EXCL;
|
||||
m_iFileDescr = shm_open(ssSharedMemName.c_str(), iMapAccess, S_IRUSR | S_IWUSR);
|
||||
}
|
||||
if (m_iFileDescr == -1)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for the size of the memory. If zero, the memory is not initialized yet.
|
||||
struct stat sStatistics{};
|
||||
int iResult = fstat(m_iFileDescr, &sStatistics);
|
||||
if (iResult == -1)
|
||||
{
|
||||
Close();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extend shared memory object as by default it's initialized with size 0
|
||||
if (!sStatistics.st_size)
|
||||
{
|
||||
// Readonly files cannot be truncated
|
||||
if (bReadOnly)
|
||||
{
|
||||
Close();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
iResult = ftruncate(m_iFileDescr, static_cast<off_t>(GetDefaultSize()));
|
||||
if (iResult != -1)
|
||||
iResult = fstat(m_iFileDescr, &sStatistics);
|
||||
if (iResult == -1 || !sStatistics.st_size)
|
||||
{
|
||||
Close();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Map the file into memory
|
||||
int iViewAccess = PROT_READ;
|
||||
if (!bOpenOnly && !bReadOnly)
|
||||
iViewAccess |= PROT_WRITE;
|
||||
void* pView = mmap(NULL, sStatistics.st_size, iViewAccess, MAP_SHARED, m_iFileDescr, 0);
|
||||
if (pView == MAP_FAILED)
|
||||
{
|
||||
Close();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Needs initialization?
|
||||
InitializeBuffer(pView, sStatistics.st_size, bReadOnly);
|
||||
}
|
||||
while (std::chrono::duration_cast<std::chrono::duration<size_t, std::milli>>(
|
||||
std::chrono::high_resolution_clock::now() - tpStart).count() < nTimeout);
|
||||
|
||||
return IsOpened();
|
||||
}
|
||||
|
||||
void CTraceFifoPosix::Close()
|
||||
{
|
||||
if (GetView()) munmap(GetView(), GetViewSize());
|
||||
if (m_iFileDescr)
|
||||
close(m_iFileDescr);
|
||||
m_iFileDescr = 0;
|
||||
Terminate();
|
||||
}
|
||||
|
||||
bool CTraceFifoPosix::IsOpened() const
|
||||
{
|
||||
return m_iFileDescr ? true : false;
|
||||
}
|
||||
|
||||
#endif // defined __unix__
|
||||
82
global/tracefifo/trace_fifo_posix.h
Normal file
82
global/tracefifo/trace_fifo_posix.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#if !defined TRACE_FIFO_POSIX_H && defined __unix__
|
||||
#define TRACE_FIFO_POSIX_H
|
||||
|
||||
#ifndef TRACE_FIFO_H
|
||||
#error Do not include this file directly. Include trace_fifo.h instead.
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Trace fifo shared memory class for Posix.
|
||||
*/
|
||||
class CTraceFifoPosix : public CTraceFifoBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
|
||||
* @param[in] nSize Size of the fifo.
|
||||
*/
|
||||
CTraceFifoPosix(uint32_t uiInstanceID = 1000u, size_t nSize = 16*1024);
|
||||
|
||||
/**
|
||||
* @brief Default destructor
|
||||
* @remarks Automatically closes the fifo if opened before.
|
||||
*/
|
||||
virtual ~CTraceFifoPosix();
|
||||
|
||||
/**
|
||||
* @brief Copy constructor is not available.
|
||||
* @param[in] rfifo Reference to the fifo to copy.
|
||||
*/
|
||||
CTraceFifoPosix(const CTraceFifoPosix& rfifo) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
*/
|
||||
CTraceFifoPosix(CTraceFifoPosix&& rfifo);
|
||||
|
||||
/**
|
||||
* @brief Copy assignment is not available.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
* @return Returns a reference to this fifo.
|
||||
*/
|
||||
CTraceFifoPosix& operator=(const CTraceFifoPosix& rfifo) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
* @return Returns a reference to this fifo.
|
||||
*/
|
||||
CTraceFifoPosix& operator=(CTraceFifoPosix&& rfifo);
|
||||
|
||||
/**
|
||||
* @brief Open the fifo. Overload of CTraceFifoBase::Open.
|
||||
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
|
||||
* established.
|
||||
* @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
|
||||
* @return Returns true when connected; false otherwise.
|
||||
*/
|
||||
virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;
|
||||
|
||||
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
|
||||
// cppcheck-suppress virtualCallInConstructor
|
||||
/**
|
||||
* @brief Cancel any running task and close an open fifo. Overload of CTraceFifoBase::Close.
|
||||
*/
|
||||
virtual void Close() override;
|
||||
|
||||
/**
|
||||
* @brief Is the fifo open for reading and writing? Overload of CTraceFifoBase::IsOpened.
|
||||
* @return Returns true when opened; false otherwise.
|
||||
*/
|
||||
virtual bool IsOpened() const override;
|
||||
|
||||
private:
|
||||
int m_iFileDescr = 0; ///< File descriptor of the shared memory.
|
||||
};
|
||||
|
||||
/// The Posix implementation of the trace fifo.
|
||||
using CTraceFifoImpl = CTraceFifoPosix;
|
||||
|
||||
#endif // !defined TRACE_FIFO_POSIX_H
|
||||
135
global/tracefifo/trace_fifo_windows.cpp
Normal file
135
global/tracefifo/trace_fifo_windows.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#if defined _WIN32
|
||||
|
||||
#ifndef INCLUDE_TRACE_FIFO_PLATFORM
|
||||
#error Do not include this file directly. The file is included by trace_fifo.cpp.
|
||||
#endif
|
||||
|
||||
#include "trace_fifo_windows.h"
|
||||
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <support/string.h>
|
||||
|
||||
CTraceFifoWindows::CTraceFifoWindows(uint32_t uiInstanceID /*= 1000u*/, size_t nSize /*= 16*1024*/) :
|
||||
CTraceFifoBase(uiInstanceID, nSize)
|
||||
{}
|
||||
|
||||
CTraceFifoWindows::~CTraceFifoWindows()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
CTraceFifoWindows::CTraceFifoWindows(CTraceFifoWindows&& rfifo) noexcept:
|
||||
CTraceFifoBase(static_cast<CTraceFifoBase&&>(rfifo)), m_hMapFile(rfifo.m_hMapFile)
|
||||
{
|
||||
rfifo.m_hMapFile = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
CTraceFifoWindows& CTraceFifoWindows::operator=(CTraceFifoWindows&& rfifo) noexcept
|
||||
{
|
||||
Close();
|
||||
CTraceFifoBase::operator=(static_cast<CTraceFifoBase&&>(rfifo));
|
||||
m_hMapFile = rfifo.m_hMapFile;
|
||||
rfifo.m_hMapFile = INVALID_HANDLE_VALUE;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// Prevent bogus warning about uninitialized memory for the variable *hFile.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 6001)
|
||||
#endif
|
||||
|
||||
bool CTraceFifoWindows::Open(size_t nTimeout /*= 1000*/, uint32_t uiFlags /*= 0u*/)
|
||||
{
|
||||
std::string ssSharedMemName = "SDV_TRACE_MONITOR_" + std::to_string(GetInstanceID());
|
||||
|
||||
auto tpStart = std::chrono::high_resolution_clock::now();
|
||||
bool bOpenOnly = uiFlags & static_cast<uint32_t>(ETraceFifoOpenFlags::open_only);
|
||||
bool bForceCreate = uiFlags & static_cast<uint32_t>(ETraceFifoOpenFlags::force_create);
|
||||
bool bReadOnly = uiFlags & static_cast<uint32_t>(ETraceFifoOpenFlags::read_only);
|
||||
if (bOpenOnly && bForceCreate) return false;
|
||||
|
||||
do
|
||||
{
|
||||
if (IsOpened()) break;
|
||||
|
||||
// Try creating the file mapping object
|
||||
if (m_hMapFile == nullptr || m_hMapFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (bOpenOnly)
|
||||
{
|
||||
DWORD dwMapAccess = bReadOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS;
|
||||
m_hMapFile = OpenFileMappingA(dwMapAccess, false, ssSharedMemName.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD dwMapAccess = PAGE_READWRITE;
|
||||
m_hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, dwMapAccess, 0, static_cast<DWORD>(GetDefaultSize()), ssSharedMemName.c_str());
|
||||
if (m_hMapFile != nullptr && m_hMapFile != INVALID_HANDLE_VALUE && bForceCreate && GetLastError() == ERROR_ALREADY_EXISTS)
|
||||
{
|
||||
CloseHandle(m_hMapFile);
|
||||
m_hMapFile = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_hMapFile == nullptr || m_hMapFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Map the file into memory
|
||||
DWORD dwViewAccess = bReadOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS;
|
||||
void* pView = MapViewOfFile(m_hMapFile, dwViewAccess, 0, 0, /*GetDefaultSize()*/0);
|
||||
if (!pView)
|
||||
{
|
||||
CloseHandle(m_hMapFile);
|
||||
m_hMapFile = INVALID_HANDLE_VALUE;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Request the size of the mapping - this will update the size...
|
||||
MEMORY_BASIC_INFORMATION sMemInfo{};
|
||||
if (VirtualQuery(pView, &sMemInfo, sizeof(sMemInfo)) != sizeof(sMemInfo) || !sMemInfo.RegionSize)
|
||||
{
|
||||
UnmapViewOfFile(pView);
|
||||
CloseHandle(m_hMapFile);
|
||||
m_hMapFile = INVALID_HANDLE_VALUE;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Needs Initialize
|
||||
InitializeBuffer(pView, sMemInfo.RegionSize, bReadOnly);
|
||||
}
|
||||
while (std::chrono::duration_cast<std::chrono::duration<size_t, std::milli>>(
|
||||
std::chrono::high_resolution_clock::now() - tpStart).count() < nTimeout);
|
||||
|
||||
return IsOpened();
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
void CTraceFifoWindows::Close()
|
||||
{
|
||||
if (GetView()) UnmapViewOfFile(GetView());
|
||||
if (m_hMapFile && m_hMapFile != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(m_hMapFile);
|
||||
m_hMapFile = INVALID_HANDLE_VALUE;
|
||||
Terminate();
|
||||
}
|
||||
|
||||
bool CTraceFifoWindows::IsOpened() const
|
||||
{
|
||||
return m_hMapFile && m_hMapFile != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
#endif // defined _WIN32
|
||||
95
global/tracefifo/trace_fifo_windows.h
Normal file
95
global/tracefifo/trace_fifo_windows.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#if !defined TRACE_FIFO_WINDOWS_H && defined _WIN32
|
||||
#define TRACE_FIFO_WINDOWS_H
|
||||
|
||||
// Resolve conflict
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <process.h>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Trace fifo shared memory class for Windows.
|
||||
*/
|
||||
class CTraceFifoWindows : public CTraceFifoBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
|
||||
* @param[in] nSize Size of the fifo.
|
||||
*/
|
||||
CTraceFifoWindows(uint32_t uiInstanceID = 1000u, size_t nSize = 16*1024);
|
||||
|
||||
/**
|
||||
* @brief Default destructor
|
||||
* @remarks Automatically closes the fifo if opened before.
|
||||
*/
|
||||
virtual ~CTraceFifoWindows() override;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor is not available.
|
||||
* @param[in] rfifo Reference to the fifo to copy.
|
||||
*/
|
||||
CTraceFifoWindows(const CTraceFifoWindows& rfifo) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
*/
|
||||
CTraceFifoWindows(CTraceFifoWindows&& rfifo) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Copy assignment is not available.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
* @return Returns a reference to this fifo.
|
||||
*/
|
||||
CTraceFifoWindows& operator=(const CTraceFifoWindows& rfifo) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment.
|
||||
* @param[in] rfifo Reference to the fifo.
|
||||
* @return Returns a reference to this fifo.
|
||||
*/
|
||||
CTraceFifoWindows& operator=(CTraceFifoWindows&& rfifo) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Open the fifo. Override of CTraceFifoBase::Open.
|
||||
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
|
||||
* established.
|
||||
* @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
|
||||
* @return Returns true when connected; false otherwise.
|
||||
*/
|
||||
virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;
|
||||
|
||||
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
|
||||
// cppcheck-suppress virtualCallInConstructor
|
||||
/**
|
||||
* @brief Cancel any running task and close an open fifo. Override of CTraceFifoBase::Close.
|
||||
*/
|
||||
virtual void Close() override;
|
||||
|
||||
/**
|
||||
* @brief Is the fifo open for reading and writing? Override of CTraceFifoBase::IsOpened.
|
||||
* @return Returns true when opened; false otherwise.
|
||||
*/
|
||||
virtual bool IsOpened() const override;
|
||||
|
||||
private:
|
||||
HANDLE m_hMapFile = INVALID_HANDLE_VALUE; ///< Handle to the shared memory buffer.
|
||||
};
|
||||
|
||||
/// The Windows implementation of the trace fifo.
|
||||
using CTraceFifoImpl = CTraceFifoWindows;
|
||||
|
||||
#endif // !defined TRACE_FIFO_WINDOWS_H
|
||||
Reference in New Issue
Block a user