initial commit
This commit is contained in:
107
support/src/RoundedRectangleShape.cpp
Normal file
107
support/src/RoundedRectangleShape.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the
|
||||
// use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include "RoundedRectangleShape.hpp"
|
||||
|
||||
#if defined(PROJECT_ENABLE_SFML)
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace sf {
|
||||
////////////////////////////////////////////////////////////
|
||||
RoundedRectangleShape::RoundedRectangleShape(const Vector2f &size, float radius,
|
||||
unsigned int cornerPointCount) {
|
||||
mySize = size;
|
||||
myRadius = radius;
|
||||
myCornerPointCount = cornerPointCount;
|
||||
update();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void RoundedRectangleShape::setSize(const Vector2f &size) {
|
||||
mySize = size;
|
||||
update();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const Vector2f &RoundedRectangleShape::getSize() const { return mySize; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void RoundedRectangleShape::setCornersRadius(float radius) {
|
||||
myRadius = radius;
|
||||
update();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
float RoundedRectangleShape::getCornersRadius() const { return myRadius; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void RoundedRectangleShape::setCornerPointCount(unsigned int count) {
|
||||
myCornerPointCount = count;
|
||||
update();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::size_t RoundedRectangleShape::getPointCount() const {
|
||||
return myCornerPointCount * 4;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
sf::Vector2f RoundedRectangleShape::getPoint(std::size_t index) const {
|
||||
if (index >= myCornerPointCount * 4)
|
||||
return sf::Vector2f(0, 0);
|
||||
|
||||
float deltaAngle = 90.0f / (myCornerPointCount - 1);
|
||||
sf::Vector2f center;
|
||||
unsigned int centerIndex = index / myCornerPointCount;
|
||||
static const float pi = 3.141592654f;
|
||||
|
||||
switch (centerIndex) {
|
||||
case 0:
|
||||
center.x = mySize.x - myRadius;
|
||||
center.y = myRadius;
|
||||
break;
|
||||
case 1:
|
||||
center.x = myRadius;
|
||||
center.y = myRadius;
|
||||
break;
|
||||
case 2:
|
||||
center.x = myRadius;
|
||||
center.y = mySize.y - myRadius;
|
||||
break;
|
||||
case 3:
|
||||
center.x = mySize.x - myRadius;
|
||||
center.y = mySize.y - myRadius;
|
||||
break;
|
||||
}
|
||||
|
||||
return sf::Vector2f(
|
||||
myRadius * cos(deltaAngle * (index - centerIndex) * pi / 180) + center.x,
|
||||
-myRadius * sin(deltaAngle * (index - centerIndex) * pi / 180) +
|
||||
center.y);
|
||||
}
|
||||
} // namespace sf
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_SFML)
|
||||
529
support/src/Text2.cpp
Normal file
529
support/src/Text2.cpp
Normal file
@@ -0,0 +1,529 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2018 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the
|
||||
// use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include "Text2.hpp"
|
||||
|
||||
#if defined(PROJECT_ENABLE_SFML)
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <SFML/Graphics/RenderTarget.hpp>
|
||||
#include <SFML/Graphics/Texture.hpp>
|
||||
|
||||
namespace {
|
||||
// Add an underline or strikethrough line to the vertex array
|
||||
void addLine(sf::VertexArray &vertices, float lineLength, float lineTop,
|
||||
const sf::Color &color, float offset, float thickness,
|
||||
float outlineThickness = 0) {
|
||||
float top = std::floor(lineTop + offset - (thickness / 2) + 0.5f);
|
||||
float bottom = top + std::floor(thickness + 0.5f);
|
||||
|
||||
vertices.append(
|
||||
sf::Vertex(sf::Vector2f(-outlineThickness, top - outlineThickness), color,
|
||||
sf::Vector2f(1, 1)));
|
||||
vertices.append(sf::Vertex(
|
||||
sf::Vector2f(lineLength + outlineThickness, top - outlineThickness),
|
||||
color, sf::Vector2f(1, 1)));
|
||||
vertices.append(
|
||||
sf::Vertex(sf::Vector2f(-outlineThickness, bottom + outlineThickness),
|
||||
color, sf::Vector2f(1, 1)));
|
||||
vertices.append(
|
||||
sf::Vertex(sf::Vector2f(-outlineThickness, bottom + outlineThickness),
|
||||
color, sf::Vector2f(1, 1)));
|
||||
vertices.append(sf::Vertex(
|
||||
sf::Vector2f(lineLength + outlineThickness, top - outlineThickness),
|
||||
color, sf::Vector2f(1, 1)));
|
||||
vertices.append(sf::Vertex(
|
||||
sf::Vector2f(lineLength + outlineThickness, bottom + outlineThickness),
|
||||
color, sf::Vector2f(1, 1)));
|
||||
}
|
||||
|
||||
// Add a glyph quad to the vertex array
|
||||
void addGlyphQuad(sf::VertexArray &vertices, sf::Vector2f position,
|
||||
const sf::Color &color, const sf::Glyph &glyph,
|
||||
float italicShear, float outlineThickness = 0) {
|
||||
float padding = 1.0;
|
||||
|
||||
float left = glyph.bounds.left - padding;
|
||||
float top = glyph.bounds.top - padding;
|
||||
float right = glyph.bounds.left + glyph.bounds.width + padding;
|
||||
float bottom = glyph.bounds.top + glyph.bounds.height + padding;
|
||||
|
||||
float u1 = static_cast<float>(glyph.textureRect.left) - padding;
|
||||
float v1 = static_cast<float>(glyph.textureRect.top) - padding;
|
||||
float u2 =
|
||||
static_cast<float>(glyph.textureRect.left + glyph.textureRect.width) +
|
||||
padding;
|
||||
float v2 =
|
||||
static_cast<float>(glyph.textureRect.top + glyph.textureRect.height) +
|
||||
padding;
|
||||
|
||||
vertices.append(sf::Vertex(
|
||||
sf::Vector2f(position.x + left - italicShear * top - outlineThickness,
|
||||
position.y + top - outlineThickness),
|
||||
color, sf::Vector2f(u1, v1)));
|
||||
vertices.append(sf::Vertex(
|
||||
sf::Vector2f(position.x + right - italicShear * top - outlineThickness,
|
||||
position.y + top - outlineThickness),
|
||||
color, sf::Vector2f(u2, v1)));
|
||||
vertices.append(sf::Vertex(
|
||||
sf::Vector2f(position.x + left - italicShear * bottom - outlineThickness,
|
||||
position.y + bottom - outlineThickness),
|
||||
color, sf::Vector2f(u1, v2)));
|
||||
vertices.append(sf::Vertex(
|
||||
sf::Vector2f(position.x + left - italicShear * bottom - outlineThickness,
|
||||
position.y + bottom - outlineThickness),
|
||||
color, sf::Vector2f(u1, v2)));
|
||||
vertices.append(sf::Vertex(
|
||||
sf::Vector2f(position.x + right - italicShear * top - outlineThickness,
|
||||
position.y + top - outlineThickness),
|
||||
color, sf::Vector2f(u2, v1)));
|
||||
vertices.append(sf::Vertex(
|
||||
sf::Vector2f(position.x + right - italicShear * bottom - outlineThickness,
|
||||
position.y + bottom - outlineThickness),
|
||||
color, sf::Vector2f(u2, v2)));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace sf {
|
||||
////////////////////////////////////////////////////////////
|
||||
Text2::Text2()
|
||||
: m_string(), m_font(NULL), m_characterSize(30), m_letterSpacingFactor(1.f),
|
||||
m_lineSpacingFactor(1.f), m_style(Regular), m_fillColor(255, 255, 255),
|
||||
m_outlineColor(0, 0, 0), m_outlineThickness(0), m_vertices(Triangles),
|
||||
m_outlineVertices(Triangles), m_bounds(), m_geometryNeedUpdate(false),
|
||||
m_tabSpaceCount(2U) {}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Text2::Text2(const String &string, const Font &font, unsigned int characterSize)
|
||||
: m_string(string), m_font(&font), m_characterSize(characterSize),
|
||||
m_letterSpacingFactor(1.f), m_lineSpacingFactor(1.f), m_style(Regular),
|
||||
m_fillColor(255, 255, 255), m_outlineColor(0, 0, 0),
|
||||
m_outlineThickness(0), m_vertices(Triangles),
|
||||
m_outlineVertices(Triangles), m_bounds(), m_geometryNeedUpdate(true),
|
||||
m_tabSpaceCount(2U) {}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::setString(const String &string) {
|
||||
if (m_string != string) {
|
||||
m_string = string;
|
||||
m_geometryNeedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::setFont(const Font &font) {
|
||||
if (m_font != &font) {
|
||||
m_font = &font;
|
||||
m_geometryNeedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::setCharacterSize(unsigned int size) {
|
||||
if (m_characterSize != size) {
|
||||
m_characterSize = size;
|
||||
m_geometryNeedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::setLetterSpacing(float spacingFactor) {
|
||||
if (m_letterSpacingFactor != spacingFactor) {
|
||||
m_letterSpacingFactor = spacingFactor;
|
||||
m_geometryNeedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::setLineSpacing(float spacingFactor) {
|
||||
if (m_lineSpacingFactor != spacingFactor) {
|
||||
m_lineSpacingFactor = spacingFactor;
|
||||
m_geometryNeedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::setStyle(Uint32 style) {
|
||||
if (m_style != style) {
|
||||
m_style = style;
|
||||
m_geometryNeedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::setColor(const Color &color) { setFillColor(color); }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::setFillColor(const Color &color) {
|
||||
if (color != m_fillColor) {
|
||||
m_fillColor = color;
|
||||
|
||||
// Change vertex colors directly, no need to update whole geometry
|
||||
// (if geometry is updated anyway, we can skip this step)
|
||||
if (!m_geometryNeedUpdate) {
|
||||
for (std::size_t i = 0; i < m_vertices.getVertexCount(); ++i)
|
||||
m_vertices[i].color = m_fillColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::setOutlineColor(const Color &color) {
|
||||
if (color != m_outlineColor) {
|
||||
m_outlineColor = color;
|
||||
|
||||
// Change vertex colors directly, no need to update whole geometry
|
||||
// (if geometry is updated anyway, we can skip this step)
|
||||
if (!m_geometryNeedUpdate) {
|
||||
for (std::size_t i = 0; i < m_outlineVertices.getVertexCount(); ++i)
|
||||
m_outlineVertices[i].color = m_outlineColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::setOutlineThickness(float thickness) {
|
||||
if (thickness != m_outlineThickness) {
|
||||
m_outlineThickness = thickness;
|
||||
m_geometryNeedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::setTabSpaceCount(Uint8 spaces) {
|
||||
spaces = std::max(Uint8(1U), spaces);
|
||||
if (m_tabSpaceCount != spaces) {
|
||||
m_tabSpaceCount = spaces;
|
||||
m_geometryNeedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const String &Text2::getString() const { return m_string; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const Font *Text2::getFont() const { return m_font; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
unsigned int Text2::getCharacterSize() const { return m_characterSize; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
float Text2::getLetterSpacing() const { return m_letterSpacingFactor; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
float Text2::getLineSpacing() const { return m_lineSpacingFactor; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Uint32 Text2::getStyle() const { return m_style; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const Color &Text2::getColor() const { return getFillColor(); }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const Color &Text2::getFillColor() const { return m_fillColor; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const Color &Text2::getOutlineColor() const { return m_outlineColor; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
float Text2::getOutlineThickness() const { return m_outlineThickness; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Vector2f Text2::findCharacterPos(std::size_t index) const {
|
||||
// Make sure that we have a valid font
|
||||
if (!m_font)
|
||||
return Vector2f();
|
||||
|
||||
// Adjust the index if it's out of range
|
||||
if (index > m_string.getSize())
|
||||
index = m_string.getSize();
|
||||
|
||||
// Precompute the variables needed by the algorithm
|
||||
bool isBold = m_style & Bold;
|
||||
float whitespaceWidth =
|
||||
m_font->getGlyph(L' ', m_characterSize, isBold).advance;
|
||||
float letterSpacing = (whitespaceWidth / 3.f) * (m_letterSpacingFactor - 1.f);
|
||||
whitespaceWidth += letterSpacing;
|
||||
float lineSpacing =
|
||||
m_font->getLineSpacing(m_characterSize) * m_lineSpacingFactor;
|
||||
|
||||
// Compute the position
|
||||
Vector2f position;
|
||||
Uint32 prevChar = 0;
|
||||
for (std::size_t i = 0; i < index; ++i) {
|
||||
Uint32 curChar = m_string[i];
|
||||
|
||||
// Apply the kerning offset
|
||||
position.x += m_font->getKerning(prevChar, curChar, m_characterSize);
|
||||
prevChar = curChar;
|
||||
|
||||
// Handle special characters
|
||||
switch (curChar) {
|
||||
case ' ':
|
||||
position.x += whitespaceWidth;
|
||||
continue;
|
||||
case '\t':
|
||||
position.x += whitespaceWidth * static_cast<float>(m_tabSpaceCount);
|
||||
continue;
|
||||
case '\n':
|
||||
position.y += lineSpacing;
|
||||
position.x = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// For regular characters, add the advance offset of the glyph
|
||||
position.x += m_font->getGlyph(curChar, m_characterSize, isBold).advance +
|
||||
letterSpacing;
|
||||
}
|
||||
|
||||
// Transform the position to global coordinates
|
||||
position = getTransform().transformPoint(position);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
FloatRect Text2::getLocalBounds() const {
|
||||
ensureGeometryUpdate();
|
||||
|
||||
return m_bounds;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
FloatRect Text2::getGlobalBounds() const {
|
||||
return getTransform().transformRect(getLocalBounds());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Uint8 Text2::getTabSpaceCount() const { return m_tabSpaceCount; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::draw(RenderTarget &target, RenderStates states) const {
|
||||
if (m_font) {
|
||||
ensureGeometryUpdate();
|
||||
|
||||
states.transform *= getTransform();
|
||||
states.texture = &m_font->getTexture(m_characterSize);
|
||||
|
||||
// Only draw the outline if there is something to draw
|
||||
if (m_outlineThickness != 0)
|
||||
target.draw(m_outlineVertices, states);
|
||||
|
||||
target.draw(m_vertices, states);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Text2::ensureGeometryUpdate() const {
|
||||
if (!m_font)
|
||||
return;
|
||||
|
||||
// Do nothing, if geometry has not changed and the font texture has not
|
||||
// changed
|
||||
if (!m_geometryNeedUpdate)
|
||||
return;
|
||||
|
||||
// Mark geometry as updated
|
||||
m_geometryNeedUpdate = false;
|
||||
|
||||
// Clear the previous geometry
|
||||
m_vertices.clear();
|
||||
m_outlineVertices.clear();
|
||||
m_bounds = FloatRect();
|
||||
|
||||
// No text: nothing to draw
|
||||
if (m_string.isEmpty())
|
||||
return;
|
||||
|
||||
// Compute values related to the text style
|
||||
bool isBold = m_style & Bold;
|
||||
bool isUnderlined = m_style & Underlined;
|
||||
bool isStrikeThrough = m_style & StrikeThrough;
|
||||
float italicShear =
|
||||
(m_style & Italic) ? 0.209f : 0.f; // 12 degrees in radians
|
||||
float underlineOffset = m_font->getUnderlinePosition(m_characterSize);
|
||||
float underlineThickness = m_font->getUnderlineThickness(m_characterSize);
|
||||
|
||||
// Compute the location of the strike through dynamically
|
||||
// We use the center point of the lowercase 'x' glyph as the reference
|
||||
// We reuse the underline thickness as the thickness of the strike through as
|
||||
// well
|
||||
FloatRect xBounds = m_font->getGlyph(L'x', m_characterSize, isBold).bounds;
|
||||
float strikeThroughOffset = xBounds.top + xBounds.height / 2.f;
|
||||
|
||||
// Precompute the variables needed by the algorithm
|
||||
float whitespaceWidth =
|
||||
m_font->getGlyph(L' ', m_characterSize, isBold).advance;
|
||||
float letterSpacing = (whitespaceWidth / 3.f) * (m_letterSpacingFactor - 1.f);
|
||||
whitespaceWidth += letterSpacing;
|
||||
float lineSpacing =
|
||||
m_font->getLineSpacing(m_characterSize) * m_lineSpacingFactor;
|
||||
float x = 0.f;
|
||||
float y = static_cast<float>(m_characterSize);
|
||||
|
||||
// Create one quad for each character
|
||||
float minX = static_cast<float>(m_characterSize);
|
||||
float minY = static_cast<float>(m_characterSize);
|
||||
float maxX = 0.f;
|
||||
float maxY = 0.f;
|
||||
Uint32 prevChar = 0;
|
||||
for (std::size_t i = 0; i < m_string.getSize(); ++i) {
|
||||
Uint32 curChar = m_string[i];
|
||||
|
||||
// Skip the \r char to avoid weird graphical issues
|
||||
if (curChar == '\r')
|
||||
continue;
|
||||
|
||||
// Apply the kerning offset
|
||||
x += m_font->getKerning(prevChar, curChar, m_characterSize);
|
||||
|
||||
// If we're using the underlined style and there's a new line, draw a line
|
||||
if (isUnderlined && (curChar == L'\n' && prevChar != L'\n')) {
|
||||
addLine(m_vertices, x, y, m_fillColor, underlineOffset,
|
||||
underlineThickness);
|
||||
|
||||
if (m_outlineThickness != 0)
|
||||
addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset,
|
||||
underlineThickness, m_outlineThickness);
|
||||
}
|
||||
|
||||
// If we're using the strike through style and there's a new line, draw a
|
||||
// line across all characters
|
||||
if (isStrikeThrough && (curChar == L'\n' && prevChar != L'\n')) {
|
||||
addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset,
|
||||
underlineThickness);
|
||||
|
||||
if (m_outlineThickness != 0)
|
||||
addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset,
|
||||
underlineThickness, m_outlineThickness);
|
||||
}
|
||||
|
||||
prevChar = curChar;
|
||||
|
||||
// Handle special characters
|
||||
if ((curChar == L' ') || (curChar == L'\n') || (curChar == L'\t')) {
|
||||
// Update the current bounds (min coordinates)
|
||||
minX = std::min(minX, x);
|
||||
minY = std::min(minY, y);
|
||||
|
||||
switch (curChar) {
|
||||
case L' ':
|
||||
x += whitespaceWidth;
|
||||
break;
|
||||
case L'\t':
|
||||
x += whitespaceWidth * static_cast<float>(m_tabSpaceCount);
|
||||
break;
|
||||
case L'\n':
|
||||
y += lineSpacing;
|
||||
x = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the current bounds (max coordinates)
|
||||
maxX = std::max(maxX, x);
|
||||
maxY = std::max(maxY, y);
|
||||
|
||||
// Next glyph, no need to create a quad for whitespace
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply the outline
|
||||
if (m_outlineThickness != 0) {
|
||||
const Glyph &glyph = m_font->getGlyph(curChar, m_characterSize, isBold,
|
||||
m_outlineThickness);
|
||||
|
||||
float left = glyph.bounds.left;
|
||||
float top = glyph.bounds.top;
|
||||
float right = glyph.bounds.left + glyph.bounds.width;
|
||||
float bottom = glyph.bounds.top + glyph.bounds.height;
|
||||
|
||||
// Add the outline glyph to the vertices
|
||||
addGlyphQuad(m_outlineVertices, Vector2f(x, y), m_outlineColor, glyph,
|
||||
italicShear, m_outlineThickness);
|
||||
|
||||
// Update the current bounds with the outlined glyph bounds
|
||||
minX =
|
||||
std::min(minX, x + left - italicShear * bottom - m_outlineThickness);
|
||||
maxX = std::max(maxX, x + right - italicShear * top - m_outlineThickness);
|
||||
minY = std::min(minY, y + top - m_outlineThickness);
|
||||
maxY = std::max(maxY, y + bottom - m_outlineThickness);
|
||||
}
|
||||
|
||||
// Extract the current glyph's description
|
||||
const Glyph &glyph = m_font->getGlyph(curChar, m_characterSize, isBold);
|
||||
|
||||
// Add the glyph to the vertices
|
||||
addGlyphQuad(m_vertices, Vector2f(x, y), m_fillColor, glyph, italicShear);
|
||||
|
||||
// Update the current bounds with the non outlined glyph bounds
|
||||
if (m_outlineThickness == 0) {
|
||||
float left = glyph.bounds.left;
|
||||
float top = glyph.bounds.top;
|
||||
float right = glyph.bounds.left + glyph.bounds.width;
|
||||
float bottom = glyph.bounds.top + glyph.bounds.height;
|
||||
|
||||
minX = std::min(minX, x + left - italicShear * bottom);
|
||||
maxX = std::max(maxX, x + right - italicShear * top);
|
||||
minY = std::min(minY, y + top);
|
||||
maxY = std::max(maxY, y + bottom);
|
||||
}
|
||||
|
||||
// Advance to the next character
|
||||
x += glyph.advance + letterSpacing;
|
||||
}
|
||||
|
||||
// If we're using the underlined style, add the last line
|
||||
if (isUnderlined && (x > 0)) {
|
||||
addLine(m_vertices, x, y, m_fillColor, underlineOffset, underlineThickness);
|
||||
|
||||
if (m_outlineThickness != 0)
|
||||
addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset,
|
||||
underlineThickness, m_outlineThickness);
|
||||
}
|
||||
|
||||
// If we're using the strike through style, add the last line across all
|
||||
// characters
|
||||
if (isStrikeThrough && (x > 0)) {
|
||||
addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset,
|
||||
underlineThickness);
|
||||
|
||||
if (m_outlineThickness != 0)
|
||||
addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset,
|
||||
underlineThickness, m_outlineThickness);
|
||||
}
|
||||
|
||||
// Update the bounding rectangle
|
||||
m_bounds.left = minX;
|
||||
m_bounds.top = minY;
|
||||
m_bounds.width = maxX - minX;
|
||||
m_bounds.height = maxY - minY;
|
||||
}
|
||||
} // namespace sf
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_SFML)
|
||||
45
support/src/backward.cpp
Normal file
45
support/src/backward.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
// Pick your poison.
|
||||
//
|
||||
// On GNU/Linux, you have few choices to get the most out of your stack trace.
|
||||
//
|
||||
// By default you get:
|
||||
// - object filename
|
||||
// - function name
|
||||
//
|
||||
// In order to add:
|
||||
// - source filename
|
||||
// - line and column numbers
|
||||
// - source code snippet (assuming the file is accessible)
|
||||
|
||||
// Install one of the following libraries then uncomment one of the macro (or
|
||||
// better, add the detection of the lib and the macro definition in your build
|
||||
// system)
|
||||
|
||||
// - apt-get install libdw-dev ...
|
||||
// - g++/clang++ -ldw ...
|
||||
// #define BACKWARD_HAS_DW 1
|
||||
|
||||
// - apt-get install binutils-dev ...
|
||||
// - g++/clang++ -lbfd ...
|
||||
// #define BACKWARD_HAS_BFD 1
|
||||
|
||||
// - apt-get install libdwarf-dev ...
|
||||
// - g++/clang++ -ldwarf ...
|
||||
// #define BACKWARD_HAS_DWARF 1
|
||||
|
||||
// Regardless of the library you choose to read the debug information,
|
||||
// for potentially more detailed stack traces you can use libunwind
|
||||
// - apt-get install libunwind-dev
|
||||
// - g++/clang++ -lunwind
|
||||
// #define BACKWARD_HAS_LIBUNWIND 1
|
||||
#include "backward.hpp"
|
||||
|
||||
#if defined(PROJECT_ENABLE_BACKWARD_CPP)
|
||||
|
||||
namespace backward {
|
||||
|
||||
backward::SignalHandling sh;
|
||||
|
||||
} // namespace backward
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_BACKWARD_CPP)
|
||||
1272
support/src/fzf.c
Normal file
1272
support/src/fzf.c
Normal file
File diff suppressed because it is too large
Load Diff
498
support/src/platform_folders.cpp
Normal file
498
support/src/platform_folders.cpp
Normal file
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
Its is under the MIT license, to encourage reuse by cut-and-paste.
|
||||
|
||||
The original files are hosted here: https://github.com/sago007/PlatformFolders
|
||||
|
||||
Copyright (c) 2015-2016 Poul Sander
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "platform_folders.hpp"
|
||||
|
||||
#if defined(PROJECT_ENABLE_SAGO_PLATFORM_FOLDERS)
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace sago {
|
||||
namespace internal {
|
||||
|
||||
/**
|
||||
* Retrives the effective user's home dir.
|
||||
* If the user is running as root we ignore the HOME environment. It works badly
|
||||
* with sudo. Writing to $HOME as root implies security concerns that a
|
||||
* multiplatform program cannot be assumed to handle.
|
||||
* @return The home directory. HOME environment is respected for non-root users
|
||||
* if it exists.
|
||||
*/
|
||||
std::string getHome() {
|
||||
std::string res;
|
||||
int uid = getuid();
|
||||
const char *homeEnv = std::getenv("HOME");
|
||||
if (uid != 0 && homeEnv) {
|
||||
// We only acknowlegde HOME if not root.
|
||||
res = homeEnv;
|
||||
return res;
|
||||
}
|
||||
struct passwd *pw = nullptr;
|
||||
struct passwd pwd;
|
||||
long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
|
||||
if (bufsize < 1) {
|
||||
bufsize = 16384;
|
||||
}
|
||||
std::vector<char> buffer;
|
||||
buffer.resize(bufsize);
|
||||
int error_code = getpwuid_r(uid, &pwd, buffer.data(), buffer.size(), &pw);
|
||||
while (error_code == ERANGE) {
|
||||
// The buffer was too small. Try again with a larger buffer.
|
||||
bufsize *= 2;
|
||||
buffer.resize(bufsize);
|
||||
error_code = getpwuid_r(uid, &pwd, buffer.data(), buffer.size(), &pw);
|
||||
}
|
||||
if (error_code) {
|
||||
throw std::runtime_error("Unable to get passwd struct.");
|
||||
}
|
||||
const char *tempRes = pw->pw_dir;
|
||||
if (!tempRes) {
|
||||
throw std::runtime_error("User has no home directory");
|
||||
}
|
||||
res = tempRes;
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace sago
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
// Make sure we don't bring in all the extra junk with windows.h
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
// stringapiset.h depends on this
|
||||
#include <windows.h>
|
||||
// For SUCCEEDED macro
|
||||
#include <winerror.h>
|
||||
// For WideCharToMultiByte
|
||||
#include <stringapiset.h>
|
||||
// For SHGetFolderPathW and various CSIDL "magic numbers"
|
||||
#include <shlobj.h>
|
||||
|
||||
namespace sago {
|
||||
namespace internal {
|
||||
|
||||
std::string win32_utf16_to_utf8(const wchar_t *wstr) {
|
||||
std::string res;
|
||||
// If the 6th parameter is 0 then WideCharToMultiByte returns the number of
|
||||
// bytes needed to store the result.
|
||||
int actualSize =
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
|
||||
if (actualSize > 0) {
|
||||
// If the converted UTF-8 string could not be in the initial buffer.
|
||||
// Allocate one that can hold it.
|
||||
std::vector<char> buffer(actualSize);
|
||||
actualSize =
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &buffer[0],
|
||||
static_cast<int>(buffer.size()), nullptr, nullptr);
|
||||
res = buffer.data();
|
||||
}
|
||||
if (actualSize == 0) {
|
||||
// WideCharToMultiByte return 0 for errors.
|
||||
throw std::runtime_error("UTF16 to UTF8 failed with error code: " +
|
||||
std::to_string(GetLastError()));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace sago
|
||||
|
||||
class FreeCoTaskMemory {
|
||||
LPWSTR pointer = NULL;
|
||||
|
||||
public:
|
||||
explicit FreeCoTaskMemory(LPWSTR pointer) : pointer(pointer) {};
|
||||
~FreeCoTaskMemory() { CoTaskMemFree(pointer); }
|
||||
};
|
||||
|
||||
static std::string GetKnownWindowsFolder(REFKNOWNFOLDERID folderId,
|
||||
const char *errorMsg) {
|
||||
LPWSTR wszPath = NULL;
|
||||
HRESULT hr;
|
||||
hr = SHGetKnownFolderPath(folderId, KF_FLAG_CREATE, NULL, &wszPath);
|
||||
FreeCoTaskMemory scopeBoundMemory(wszPath);
|
||||
|
||||
if (!SUCCEEDED(hr)) {
|
||||
throw std::runtime_error(errorMsg);
|
||||
}
|
||||
return sago::internal::win32_utf16_to_utf8(wszPath);
|
||||
}
|
||||
|
||||
static std::string GetAppData() {
|
||||
return GetKnownWindowsFolder(FOLDERID_RoamingAppData,
|
||||
"RoamingAppData could not be found");
|
||||
}
|
||||
|
||||
static std::string GetAppDataCommon() {
|
||||
return GetKnownWindowsFolder(FOLDERID_ProgramData,
|
||||
"ProgramData could not be found");
|
||||
}
|
||||
|
||||
static std::string GetAppDataLocal() {
|
||||
return GetKnownWindowsFolder(FOLDERID_LocalAppData,
|
||||
"LocalAppData could not be found");
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
#else
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sys/types.h>
|
||||
// For strlen and strtok
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
// Typically Linux. For easy reading the comments will just say Linux but should
|
||||
// work with most *nixes
|
||||
|
||||
static void throwOnRelative(const char *envName, const char *envValue) {
|
||||
if (envValue[0] != '/') {
|
||||
char buffer[200];
|
||||
std::snprintf(
|
||||
buffer, sizeof(buffer),
|
||||
"Environment \"%s\" does not start with an '/'. XDG specifies that the "
|
||||
"value must be absolute. The current value is: \"%s\"",
|
||||
envName, envValue);
|
||||
throw std::runtime_error(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static std::string getLinuxFolderDefault(const char *envName,
|
||||
const char *defaultRelativePath) {
|
||||
std::string res;
|
||||
const char *tempRes = std::getenv(envName);
|
||||
if (tempRes) {
|
||||
throwOnRelative(envName, tempRes);
|
||||
res = tempRes;
|
||||
return res;
|
||||
}
|
||||
res = sago::internal::getHome() + "/" + defaultRelativePath;
|
||||
return res;
|
||||
}
|
||||
|
||||
static void appendExtraFolders(const char *envName, const char *defaultValue,
|
||||
std::vector<std::string> &folders) {
|
||||
const char *envValue = std::getenv(envName);
|
||||
if (!envValue) {
|
||||
envValue = defaultValue;
|
||||
}
|
||||
sago::internal::appendExtraFoldersTokenizer(envName, envValue, folders);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace sago {
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
namespace internal {
|
||||
void appendExtraFoldersTokenizer(const char *envName, const char *envValue,
|
||||
std::vector<std::string> &folders) {
|
||||
std::stringstream ss(envValue);
|
||||
std::string value;
|
||||
while (std::getline(ss, value, ':')) {
|
||||
if (value[0] == '/') {
|
||||
folders.push_back(value);
|
||||
} else {
|
||||
// Unless the system is wrongly configured this should never happen... But
|
||||
// of course some systems will be incorectly configured. The XDG
|
||||
// documentation indicates that the folder should be ignored but that the
|
||||
// program should continue.
|
||||
std::cerr << "Skipping path \"" << value << "\" in \"" << envName
|
||||
<< "\" because it does not start with a \"/\"\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace internal
|
||||
#endif
|
||||
|
||||
std::string getDataHome() {
|
||||
#ifdef _WIN32
|
||||
return GetAppData();
|
||||
#elif defined(__APPLE__)
|
||||
return sago::internal::getHome() + "/Library/Application Support";
|
||||
#else
|
||||
return getLinuxFolderDefault("XDG_DATA_HOME", ".local/share");
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string getConfigHome() {
|
||||
#ifdef _WIN32
|
||||
return GetAppData();
|
||||
#elif defined(__APPLE__)
|
||||
return sago::internal::getHome() + "/Library/Application Support";
|
||||
#else
|
||||
return getLinuxFolderDefault("XDG_CONFIG_HOME", ".config");
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string getCacheDir() {
|
||||
#ifdef _WIN32
|
||||
return GetAppDataLocal();
|
||||
#elif defined(__APPLE__)
|
||||
return sago::internal::getHome() + "/Library/Caches";
|
||||
#else
|
||||
return getLinuxFolderDefault("XDG_CACHE_HOME", ".cache");
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string getStateDir() {
|
||||
#ifdef _WIN32
|
||||
return GetAppDataLocal();
|
||||
#elif defined(__APPLE__)
|
||||
return sago::internal::getHome() + "/Library/Application Support";
|
||||
#else
|
||||
return getLinuxFolderDefault("XDG_STATE_HOME", ".local/state");
|
||||
#endif
|
||||
}
|
||||
|
||||
void appendAdditionalDataDirectories(std::vector<std::string> &homes) {
|
||||
#ifdef _WIN32
|
||||
homes.push_back(GetAppDataCommon());
|
||||
#elif !defined(__APPLE__)
|
||||
appendExtraFolders("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/", homes);
|
||||
#endif
|
||||
}
|
||||
|
||||
void appendAdditionalConfigDirectories(std::vector<std::string> &homes) {
|
||||
#ifdef _WIN32
|
||||
homes.push_back(GetAppDataCommon());
|
||||
#elif !defined(__APPLE__)
|
||||
appendExtraFolders("XDG_CONFIG_DIRS", "/etc/xdg", homes);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
struct PlatformFolders::PlatformFoldersData {
|
||||
std::map<std::string, std::string> folders;
|
||||
};
|
||||
|
||||
static void
|
||||
PlatformFoldersAddFromFile(const std::string &filename,
|
||||
std::map<std::string, std::string> &folders) {
|
||||
std::ifstream infile(filename.c_str());
|
||||
std::string line;
|
||||
while (std::getline(infile, line)) {
|
||||
if (line.length() == 0 || line.at(0) == '#' ||
|
||||
line.substr(0, 4) != "XDG_" || line.find("_DIR") == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
std::size_t splitPos = line.find('=');
|
||||
std::string key = line.substr(0, splitPos);
|
||||
std::size_t valueStart = line.find('"', splitPos);
|
||||
std::size_t valueEnd = line.find('"', valueStart + 1);
|
||||
std::string value =
|
||||
line.substr(valueStart + 1, valueEnd - valueStart - 1);
|
||||
folders[key] = value;
|
||||
} catch (std::exception &e) {
|
||||
std::cerr << "WARNING: Failed to process \"" << line << "\" from \""
|
||||
<< filename << "\". Error: " << e.what() << "\n";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
PlatformFoldersFillData(std::map<std::string, std::string> &folders) {
|
||||
folders["XDG_DOCUMENTS_DIR"] = "$HOME/Documents";
|
||||
folders["XDG_DESKTOP_DIR"] = "$HOME/Desktop";
|
||||
folders["XDG_DOWNLOAD_DIR"] = "$HOME/Downloads";
|
||||
folders["XDG_MUSIC_DIR"] = "$HOME/Music";
|
||||
folders["XDG_PICTURES_DIR"] = "$HOME/Pictures";
|
||||
folders["XDG_PUBLICSHARE_DIR"] = "$HOME/Public";
|
||||
folders["XDG_TEMPLATES_DIR"] = "$HOME/.Templates";
|
||||
folders["XDG_VIDEOS_DIR"] = "$HOME/Videos";
|
||||
PlatformFoldersAddFromFile(getConfigHome() + "/user-dirs.dirs", folders);
|
||||
for (std::map<std::string, std::string>::iterator itr = folders.begin();
|
||||
itr != folders.end(); ++itr) {
|
||||
std::string &value = itr->second;
|
||||
if (value.compare(0, 5, "$HOME") == 0) {
|
||||
value = sago::internal::getHome() + value.substr(5, std::string::npos);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
PlatformFolders::PlatformFolders() {
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
this->data = new PlatformFolders::PlatformFoldersData();
|
||||
try {
|
||||
PlatformFoldersFillData(data->folders);
|
||||
} catch (...) {
|
||||
delete this->data;
|
||||
throw;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
PlatformFolders::~PlatformFolders() {
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
delete this->data;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string PlatformFolders::getDocumentsFolder() const {
|
||||
#ifdef _WIN32
|
||||
return GetKnownWindowsFolder(FOLDERID_Documents,
|
||||
"Failed to find My Documents folder");
|
||||
#elif defined(__APPLE__)
|
||||
return sago::internal::getHome() + "/Documents";
|
||||
#else
|
||||
return data->folders["XDG_DOCUMENTS_DIR"];
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string PlatformFolders::getDesktopFolder() const {
|
||||
#ifdef _WIN32
|
||||
return GetKnownWindowsFolder(FOLDERID_Desktop,
|
||||
"Failed to find Desktop folder");
|
||||
#elif defined(__APPLE__)
|
||||
return sago::internal::getHome() + "/Desktop";
|
||||
#else
|
||||
return data->folders["XDG_DESKTOP_DIR"];
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string PlatformFolders::getPicturesFolder() const {
|
||||
#ifdef _WIN32
|
||||
return GetKnownWindowsFolder(FOLDERID_Pictures,
|
||||
"Failed to find My Pictures folder");
|
||||
#elif defined(__APPLE__)
|
||||
return sago::internal::getHome() + "/Pictures";
|
||||
#else
|
||||
return data->folders["XDG_PICTURES_DIR"];
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string PlatformFolders::getPublicFolder() const {
|
||||
#ifdef _WIN32
|
||||
return GetKnownWindowsFolder(FOLDERID_Public,
|
||||
"Failed to find the Public folder");
|
||||
#elif defined(__APPLE__)
|
||||
return sago::internal::getHome() + "/Public";
|
||||
#else
|
||||
return data->folders["XDG_PUBLICSHARE_DIR"];
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string PlatformFolders::getDownloadFolder1() const {
|
||||
#ifdef _WIN32
|
||||
return GetKnownWindowsFolder(FOLDERID_Downloads,
|
||||
"Failed to find My Downloads folder");
|
||||
#elif defined(__APPLE__)
|
||||
return sago::internal::getHome() + "/Downloads";
|
||||
#else
|
||||
return data->folders["XDG_DOWNLOAD_DIR"];
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string PlatformFolders::getMusicFolder() const {
|
||||
#ifdef _WIN32
|
||||
return GetKnownWindowsFolder(FOLDERID_Music,
|
||||
"Failed to find My Music folder");
|
||||
#elif defined(__APPLE__)
|
||||
return sago::internal::getHome() + "/Music";
|
||||
#else
|
||||
return data->folders["XDG_MUSIC_DIR"];
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string PlatformFolders::getVideoFolder() const {
|
||||
#ifdef _WIN32
|
||||
return GetKnownWindowsFolder(FOLDERID_Videos,
|
||||
"Failed to find My Video folder");
|
||||
#elif defined(__APPLE__)
|
||||
return sago::internal::getHome() + "/Movies";
|
||||
#else
|
||||
return data->folders["XDG_VIDEOS_DIR"];
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string PlatformFolders::getSaveGamesFolder1() const {
|
||||
#ifdef _WIN32
|
||||
// A dedicated Save Games folder was not introduced until Vista. For XP and
|
||||
// older save games are most often saved in a normal folder named "My Games".
|
||||
// Data that should not be user accessible should be placed under
|
||||
// GetDataHome() instead
|
||||
return GetKnownWindowsFolder(FOLDERID_Documents,
|
||||
"Failed to find My Documents folder") +
|
||||
"\\My Games";
|
||||
#elif defined(__APPLE__)
|
||||
return sago::internal::getHome() + "/Library/Application Support";
|
||||
#else
|
||||
return getDataHome();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string getDesktopFolder() { return PlatformFolders().getDesktopFolder(); }
|
||||
|
||||
std::string getDocumentsFolder() {
|
||||
return PlatformFolders().getDocumentsFolder();
|
||||
}
|
||||
|
||||
std::string getDownloadFolder() {
|
||||
return PlatformFolders().getDownloadFolder1();
|
||||
}
|
||||
|
||||
std::string getDownloadFolder1() { return getDownloadFolder(); }
|
||||
|
||||
std::string getPicturesFolder() {
|
||||
return PlatformFolders().getPicturesFolder();
|
||||
}
|
||||
|
||||
std::string getPublicFolder() { return PlatformFolders().getPublicFolder(); }
|
||||
|
||||
std::string getMusicFolder() { return PlatformFolders().getMusicFolder(); }
|
||||
|
||||
std::string getVideoFolder() { return PlatformFolders().getVideoFolder(); }
|
||||
|
||||
std::string getSaveGamesFolder1() {
|
||||
return PlatformFolders().getSaveGamesFolder1();
|
||||
}
|
||||
|
||||
std::string getSaveGamesFolder2() {
|
||||
#ifdef _WIN32
|
||||
return GetKnownWindowsFolder(FOLDERID_SavedGames,
|
||||
"Failed to find Saved Games folder");
|
||||
#else
|
||||
return PlatformFolders().getSaveGamesFolder1();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace sago
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_SAGO_PLATFORM_FOLDERS)
|
||||
198
support/src/utils/common.cpp
Normal file
198
support/src/utils/common.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "utils/common.hpp"
|
||||
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace fifthgrid::utils {
|
||||
auto compare_version_strings(std::string version1, std::string version2)
|
||||
-> std::int32_t {
|
||||
|
||||
if (utils::string::contains(version1, "-")) {
|
||||
version1 = utils::string::split(version1, '-', true)[0U];
|
||||
}
|
||||
|
||||
if (utils::string::contains(version2, "-")) {
|
||||
version2 = utils::string::split(version2, '-', true)[0U];
|
||||
}
|
||||
|
||||
auto nums1 = utils::string::split(version1, '.', true);
|
||||
auto nums2 = utils::string::split(version2, '.', true);
|
||||
|
||||
while (nums1.size() > nums2.size()) {
|
||||
nums2.emplace_back("0");
|
||||
}
|
||||
|
||||
while (nums2.size() > nums1.size()) {
|
||||
nums1.emplace_back("0");
|
||||
}
|
||||
|
||||
for (std::size_t idx = 0U; idx < nums1.size(); idx++) {
|
||||
auto int1 = utils::string::to_uint32(nums1[idx]);
|
||||
auto int2 = utils::string::to_uint32(nums2[idx]);
|
||||
auto res = std::memcmp(&int1, &int2, sizeof(int1));
|
||||
if (res != 0) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto compare_version_strings(std::wstring_view version1,
|
||||
std::wstring_view version2) -> std::int32_t {
|
||||
return compare_version_strings(utils::string::to_utf8(version1),
|
||||
utils::string::to_utf8(version2));
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_STDUUID)
|
||||
auto create_uuid_string() -> std::string {
|
||||
std::random_device random_device{};
|
||||
auto seed_data = std::array<int, std::mt19937::state_size>{};
|
||||
std::generate(std::begin(seed_data), std::end(seed_data),
|
||||
std::ref(random_device));
|
||||
std::seed_seq seq(std::begin(seed_data), std::end(seed_data));
|
||||
std::mt19937 generator(seq);
|
||||
uuids::uuid_random_generator gen{generator};
|
||||
|
||||
return uuids::to_string(gen());
|
||||
}
|
||||
|
||||
auto create_uuid_wstring() -> std::wstring {
|
||||
return utils::string::from_utf8(create_uuid_string());
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_STDUUID)
|
||||
|
||||
auto generate_random_string(std::size_t length) -> std::string {
|
||||
std::string ret;
|
||||
if (length == 0U) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.resize(length);
|
||||
for (auto &ch : ret) {
|
||||
std::array<unsigned int, 3U> random_list{
|
||||
generate_random_between(0U, 57U),
|
||||
generate_random_between(65U, 90U),
|
||||
generate_random_between(97U, 255U),
|
||||
};
|
||||
ch = static_cast<char>(
|
||||
random_list.at(generate_random_between(0U, 2U)) % 74U + 48U);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto generate_random_wstring(std::size_t length) -> std::wstring {
|
||||
return utils::string::from_utf8(generate_random_string(length));
|
||||
}
|
||||
|
||||
auto get_environment_variable(std::string_view variable) -> std::string {
|
||||
static std::mutex mtx{};
|
||||
mutex_lock lock{mtx};
|
||||
|
||||
const auto *val = std::getenv(std::string{variable}.c_str());
|
||||
return std::string{val == nullptr ? "" : val};
|
||||
}
|
||||
|
||||
auto get_environment_variable(std::wstring_view variable) -> std::wstring {
|
||||
return utils::string::from_utf8(
|
||||
get_environment_variable(utils::string::to_utf8(variable)));
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_BOOST)
|
||||
auto get_next_available_port(std::uint16_t first_port,
|
||||
std::uint16_t &available_port) -> bool {
|
||||
if (first_port == 0U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using namespace boost::asio;
|
||||
using ip::tcp;
|
||||
|
||||
boost::system::error_code error_code{};
|
||||
|
||||
std::uint32_t check_port{first_port};
|
||||
while (check_port <= 65535U) {
|
||||
{
|
||||
io_context ctx{};
|
||||
tcp::socket socket(ctx);
|
||||
socket.connect(
|
||||
{
|
||||
tcp::endpoint(ip::address_v4::loopback(),
|
||||
static_cast<std::uint16_t>(check_port)),
|
||||
},
|
||||
error_code);
|
||||
if (not error_code) {
|
||||
++check_port;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
io_context ctx{};
|
||||
tcp::acceptor acceptor(ctx);
|
||||
acceptor.open(tcp::v4(), error_code);
|
||||
if (error_code) {
|
||||
++check_port;
|
||||
continue;
|
||||
}
|
||||
|
||||
acceptor.set_option(boost::asio::ip::tcp::acceptor::linger(true, 0));
|
||||
acceptor.bind({tcp::v4(), static_cast<std::uint16_t>(check_port)},
|
||||
error_code);
|
||||
if (error_code) {
|
||||
++check_port;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
available_port = static_cast<std::uint16_t>(check_port);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_BOOST)
|
||||
|
||||
auto retry_action(retryable_action_t action, std::size_t retry_count,
|
||||
std::chrono::milliseconds retry_wait) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
for (std::size_t idx = 0U; idx < retry_count; ++idx) {
|
||||
if (action()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(retry_wait);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace fifthgrid::utils
|
||||
250
support/src/utils/db/sqlite/db_common.cpp
Normal file
250
support/src/utils/db/sqlite/db_common.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(PROJECT_ENABLE_SQLITE)
|
||||
|
||||
#include "utils/db/sqlite/db_common.hpp"
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
|
||||
namespace fifthgrid::utils::db::sqlite {
|
||||
void sqlite3_deleter::operator()(sqlite3 *db3) const {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (db3 == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string err_msg;
|
||||
if (not execute_sql(*db3, "VACUUM;", err_msg)) {
|
||||
utils::error::handle_error(function_name,
|
||||
utils::error::create_error_message({
|
||||
"failed to vacuum database",
|
||||
err_msg,
|
||||
}));
|
||||
}
|
||||
|
||||
if (not utils::retry_action([&db3]() -> bool {
|
||||
auto res = sqlite3_close_v2(db3);
|
||||
if (res == SQLITE_OK) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto &&err_str = sqlite3_errstr(res);
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
utils::error::create_error_message({
|
||||
"failed to close database",
|
||||
(err_str == nullptr ? std::to_string(res) : err_str),
|
||||
}));
|
||||
return false;
|
||||
})) {
|
||||
fifthgrid::utils::error::handle_error(function_name,
|
||||
"failed to close database");
|
||||
}
|
||||
}
|
||||
|
||||
db_result::db_column::db_column(std::int32_t index, std::string name,
|
||||
db_types_t value) noexcept
|
||||
: index_(index), name_(std::move(name)), value_(std::move(value)) {}
|
||||
|
||||
#if defined(PROJECT_ENABLE_JSON)
|
||||
auto db_result::db_column::get_value_as_json() const -> nlohmann::json {
|
||||
return std::visit(
|
||||
overloaded{
|
||||
[this](std::int64_t value) -> auto {
|
||||
return nlohmann::json({{name_, value}});
|
||||
},
|
||||
[](auto &&value) -> auto { return nlohmann::json::parse(value); },
|
||||
},
|
||||
value_);
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_JSON)
|
||||
|
||||
db_result::db_row::db_row(std::shared_ptr<context> ctx) {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
auto column_count = sqlite3_column_count(ctx->stmt.get());
|
||||
for (std::int32_t col = 0; col < column_count; col++) {
|
||||
std::string name{sqlite3_column_name(ctx->stmt.get(), col)};
|
||||
auto column_type = sqlite3_column_type(ctx->stmt.get(), col);
|
||||
|
||||
db_types_t value;
|
||||
switch (column_type) {
|
||||
case SQLITE_INTEGER: {
|
||||
value = sqlite3_column_int64(ctx->stmt.get(), col);
|
||||
} break;
|
||||
|
||||
case SQLITE_TEXT: {
|
||||
const auto *text = reinterpret_cast<const char *>(
|
||||
sqlite3_column_text(ctx->stmt.get(), col));
|
||||
value = std::string(text == nullptr ? "" : text);
|
||||
} break;
|
||||
|
||||
default:
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"column type not implemented",
|
||||
std::to_string(column_type),
|
||||
});
|
||||
}
|
||||
|
||||
columns_[name] = db_column{col, name, value};
|
||||
}
|
||||
}
|
||||
|
||||
auto db_result::db_row::get_columns() const -> std::vector<db_column> {
|
||||
std::vector<db_column> ret;
|
||||
for (const auto &item : columns_) {
|
||||
ret.push_back(item.second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto db_result::db_row::get_column(std::int32_t index) const -> db_column {
|
||||
auto iter = std::find_if(
|
||||
columns_.begin(), columns_.end(),
|
||||
[&index](auto &&col) -> bool { return col.second.get_index() == index; });
|
||||
if (iter == columns_.end()) {
|
||||
throw std::out_of_range("");
|
||||
}
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
auto db_result::db_row::get_column(std::string name) const -> db_column {
|
||||
return columns_.at(name);
|
||||
}
|
||||
|
||||
db_result::db_result(db3_stmt_t stmt, std::int32_t res)
|
||||
: ctx_(std::make_shared<context>()), res_(res) {
|
||||
ctx_->stmt = std::move(stmt);
|
||||
if (res == SQLITE_OK) {
|
||||
set_res(sqlite3_step(ctx_->stmt.get()));
|
||||
}
|
||||
}
|
||||
|
||||
auto db_result::get_error_str() const -> std::string {
|
||||
auto &&err_msg = sqlite3_errstr(res_);
|
||||
return err_msg == nullptr ? std::to_string(res_) : err_msg;
|
||||
}
|
||||
|
||||
auto db_result::get_row(std::optional<row> &opt_row) const -> bool {
|
||||
opt_row.reset();
|
||||
|
||||
if (not has_row()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
opt_row = db_row{ctx_};
|
||||
set_res(sqlite3_step(ctx_->stmt.get()));
|
||||
return true;
|
||||
}
|
||||
|
||||
auto db_result::has_row() const -> bool { return res_ == SQLITE_ROW; }
|
||||
|
||||
void db_result::next_row() const {
|
||||
if (not has_row()) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_res(sqlite3_step(ctx_->stmt.get()));
|
||||
}
|
||||
|
||||
auto db_result::ok() const -> bool {
|
||||
return res_ == SQLITE_DONE || res_ == SQLITE_ROW;
|
||||
}
|
||||
|
||||
auto create_db(std::string db_path,
|
||||
const std::map<std::string, std::string> &sql_create_tables)
|
||||
-> db3_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
sqlite3 *db_ptr{nullptr};
|
||||
auto db_res =
|
||||
sqlite3_open_v2(db_path.c_str(), &db_ptr,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
|
||||
if (db_res != SQLITE_OK) {
|
||||
const auto *msg = sqlite3_errstr(db_res);
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to open db",
|
||||
db_path,
|
||||
(msg == nullptr ? std::to_string(db_res) : msg),
|
||||
});
|
||||
}
|
||||
|
||||
auto db3 = db3_t{
|
||||
db_ptr,
|
||||
sqlite3_deleter(),
|
||||
};
|
||||
|
||||
for (auto &&create_item : sql_create_tables) {
|
||||
std::string err_msg;
|
||||
if (not sqlite::execute_sql(*db3, create_item.second, err_msg)) {
|
||||
db3.reset();
|
||||
throw utils::error::create_exception(function_name, {
|
||||
err_msg,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
set_journal_mode(*db3);
|
||||
|
||||
return db3;
|
||||
}
|
||||
|
||||
auto execute_sql(sqlite3 &db3, const std::string &sql, std::string &err)
|
||||
-> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
char *err_msg{nullptr};
|
||||
auto res = sqlite3_exec(&db3, sql.c_str(), nullptr, nullptr, &err_msg);
|
||||
if (err_msg != nullptr) {
|
||||
err = err_msg;
|
||||
sqlite3_free(err_msg);
|
||||
err_msg = nullptr;
|
||||
}
|
||||
|
||||
if (res == SQLITE_OK) {
|
||||
return true;
|
||||
}
|
||||
|
||||
err = utils::error::create_error_message({
|
||||
function_name,
|
||||
"failed to execute sql",
|
||||
err,
|
||||
sql,
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_journal_mode(sqlite3 &db3) {
|
||||
sqlite3_exec(&db3,
|
||||
"PRAGMA journal_mode = WAL;PRAGMA synchronous = NORMAL;PRAGMA "
|
||||
"auto_vacuum = NONE;",
|
||||
nullptr, nullptr, nullptr);
|
||||
}
|
||||
} // namespace fifthgrid::utils::db::sqlite
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_SQLITE)
|
||||
115
support/src/utils/db/sqlite/db_delete.cpp
Normal file
115
support/src/utils/db/sqlite/db_delete.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(PROJECT_ENABLE_SQLITE)
|
||||
|
||||
#include "utils/db/sqlite/db_delete.hpp"
|
||||
|
||||
namespace fifthgrid::utils::db::sqlite {
|
||||
auto db_delete::context::db_delete_op_t::dump() const -> std::string {
|
||||
return db_delete{ctx}.dump();
|
||||
}
|
||||
|
||||
auto db_delete::context::db_delete_op_t::go() const -> db_result {
|
||||
return db_delete{ctx}.go();
|
||||
}
|
||||
|
||||
auto db_delete::dump() const -> std::string {
|
||||
std::stringstream query;
|
||||
query << "DELETE FROM \"" << ctx_->table_name << "\"";
|
||||
|
||||
if (ctx_->where_data) {
|
||||
std::int32_t idx{};
|
||||
query << " WHERE " << ctx_->where_data->base.dump(idx);
|
||||
}
|
||||
|
||||
query << ';';
|
||||
|
||||
return query.str();
|
||||
}
|
||||
|
||||
auto db_delete::go() const -> db_result {
|
||||
sqlite3_stmt *stmt_ptr{nullptr};
|
||||
auto query_str = dump();
|
||||
auto res =
|
||||
sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr);
|
||||
|
||||
auto stmt = db3_stmt_t{
|
||||
stmt_ptr,
|
||||
sqlite3_statement_deleter(),
|
||||
};
|
||||
|
||||
if (res != SQLITE_OK) {
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
|
||||
if (not ctx_->where_data) {
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
|
||||
for (std::int32_t idx = 0;
|
||||
idx < static_cast<std::int32_t>(ctx_->where_data->values.size());
|
||||
idx++) {
|
||||
res =
|
||||
std::visit(overloaded{
|
||||
[&stmt, &idx](std::int64_t data) -> std::int32_t {
|
||||
return sqlite3_bind_int64(stmt.get(), idx + 1, data);
|
||||
},
|
||||
[&stmt, &idx](const std::string &data) -> std::int32_t {
|
||||
return sqlite3_bind_text(stmt.get(), idx + 1,
|
||||
data.c_str(), -1, nullptr);
|
||||
},
|
||||
},
|
||||
ctx_->where_data->values.at(static_cast<std::size_t>(idx)));
|
||||
if (res != SQLITE_OK) {
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
}
|
||||
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
|
||||
auto db_delete::group(context::w_t::group_func_t func) -> context::w_t::wn_t {
|
||||
if (not ctx_->where_data) {
|
||||
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
|
||||
context::w_t{0U, ctx_},
|
||||
{},
|
||||
{},
|
||||
});
|
||||
}
|
||||
|
||||
return ctx_->where_data->base.group(std::move(func));
|
||||
}
|
||||
|
||||
auto db_delete::where(std::string column_name) const -> context::w_t::cn_t {
|
||||
if (not ctx_->where_data) {
|
||||
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
|
||||
context::w_t{0U, ctx_},
|
||||
{},
|
||||
{},
|
||||
});
|
||||
}
|
||||
|
||||
return ctx_->where_data->base.where(column_name);
|
||||
}
|
||||
} // namespace fifthgrid::utils::db::sqlite
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_SQLITE)
|
||||
99
support/src/utils/db/sqlite/db_insert.cpp
Normal file
99
support/src/utils/db/sqlite/db_insert.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(PROJECT_ENABLE_SQLITE)
|
||||
|
||||
#include "utils/db/sqlite/db_insert.hpp"
|
||||
|
||||
namespace fifthgrid::utils::db::sqlite {
|
||||
auto db_insert::column_value(std::string column_name, db_types_t value)
|
||||
-> db_insert {
|
||||
ctx_->values[column_name] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_insert::dump() const -> std::string {
|
||||
std::stringstream query;
|
||||
query << "INSERT ";
|
||||
if (ctx_->or_replace) {
|
||||
query << "OR REPLACE ";
|
||||
}
|
||||
query << "INTO \"" << ctx_->table_name << "\" (";
|
||||
|
||||
for (std::int32_t idx = 0;
|
||||
idx < static_cast<std::int32_t>(ctx_->values.size()); idx++) {
|
||||
if (idx > 0) {
|
||||
query << ", ";
|
||||
}
|
||||
query << '"' << std::next(ctx_->values.begin(), idx)->first << '"';
|
||||
}
|
||||
|
||||
query << ") VALUES (";
|
||||
for (std::int32_t idx = 0;
|
||||
idx < static_cast<std::int32_t>(ctx_->values.size()); idx++) {
|
||||
if (idx > 0) {
|
||||
query << ", ";
|
||||
}
|
||||
query << "?" << (idx + 1);
|
||||
}
|
||||
query << ");";
|
||||
|
||||
return query.str();
|
||||
}
|
||||
|
||||
auto db_insert::go() const -> db_result {
|
||||
sqlite3_stmt *stmt_ptr{nullptr};
|
||||
auto query_str = dump();
|
||||
auto res =
|
||||
sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr);
|
||||
|
||||
auto stmt = db3_stmt_t{
|
||||
stmt_ptr,
|
||||
sqlite3_statement_deleter(),
|
||||
};
|
||||
|
||||
if (res != SQLITE_OK) {
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
|
||||
for (std::int32_t idx = 0;
|
||||
idx < static_cast<std::int32_t>(ctx_->values.size()); idx++) {
|
||||
res =
|
||||
std::visit(overloaded{
|
||||
[&idx, &stmt](std::int64_t data) -> std::int32_t {
|
||||
return sqlite3_bind_int64(stmt.get(), idx + 1, data);
|
||||
},
|
||||
[&idx, &stmt](const std::string &data) -> std::int32_t {
|
||||
return sqlite3_bind_text(stmt.get(), idx + 1,
|
||||
data.c_str(), -1, nullptr);
|
||||
},
|
||||
},
|
||||
std::next(ctx_->values.begin(), idx)->second);
|
||||
if (res != SQLITE_OK) {
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
}
|
||||
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
} // namespace fifthgrid::utils::db::sqlite
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_SQLITE)
|
||||
221
support/src/utils/db/sqlite/db_select.cpp
Normal file
221
support/src/utils/db/sqlite/db_select.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(PROJECT_ENABLE_SQLITE)
|
||||
|
||||
#include "utils/db/sqlite/db_select.hpp"
|
||||
|
||||
namespace fifthgrid::utils::db::sqlite {
|
||||
auto db_select::context::db_select_op_t::dump() const -> std::string {
|
||||
return db_select{ctx}.dump();
|
||||
}
|
||||
|
||||
auto db_select::context::db_select_op_t::go() const -> db_result {
|
||||
return db_select{ctx}.go();
|
||||
}
|
||||
|
||||
auto db_select::context::db_select_op_t::group_by(std::string column_name)
|
||||
-> db_select::context::db_select_op_t {
|
||||
db_select{ctx}.group_by(column_name);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_select::context::db_select_op_t::limit(std::int32_t value)
|
||||
-> db_select::context::db_select_op_t {
|
||||
db_select{ctx}.limit(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_select::context::db_select_op_t::offset(std::int32_t value)
|
||||
-> db_select::context::db_select_op_t {
|
||||
db_select{ctx}.offset(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_select::context::db_select_op_t::order_by(std::string column_name,
|
||||
bool ascending)
|
||||
-> db_select::context::db_select_op_t {
|
||||
db_select{ctx}.order_by(column_name, ascending);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_select::column(std::string column_name) -> db_select {
|
||||
ctx_->columns.push_back(column_name);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_select::count(std::string column_name, std::string as_column_name)
|
||||
-> db_select {
|
||||
ctx_->count_columns[column_name] = as_column_name;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_select::dump() const -> std::string {
|
||||
std::stringstream query;
|
||||
query << "SELECT ";
|
||||
bool has_column{false};
|
||||
if (ctx_->columns.empty()) {
|
||||
if (ctx_->count_columns.empty()) {
|
||||
query << "*";
|
||||
has_column = true;
|
||||
}
|
||||
} else {
|
||||
has_column = not ctx_->columns.empty();
|
||||
for (std::size_t idx = 0U; idx < ctx_->columns.size(); idx++) {
|
||||
if (idx > 0U) {
|
||||
query << ", ";
|
||||
}
|
||||
query << ctx_->columns.at(idx);
|
||||
}
|
||||
}
|
||||
|
||||
for (std::int32_t idx = 0U;
|
||||
idx < static_cast<std::int32_t>(ctx_->count_columns.size()); idx++) {
|
||||
if (has_column || idx > 0) {
|
||||
query << ", ";
|
||||
}
|
||||
query << "COUNT(\"";
|
||||
auto &count_column = *std::next(ctx_->count_columns.begin(), idx);
|
||||
query << count_column.first << "\") AS \"" << count_column.second << '"';
|
||||
}
|
||||
query << " FROM \"" << ctx_->table_name << "\"";
|
||||
|
||||
if (ctx_->where_data) {
|
||||
std::int32_t idx{};
|
||||
query << " WHERE " << ctx_->where_data->base.dump(idx);
|
||||
}
|
||||
|
||||
if (not ctx_->group_by.empty()) {
|
||||
query << " GROUP BY ";
|
||||
for (std::size_t idx = 0U; idx < ctx_->group_by.size(); idx++) {
|
||||
if (idx > 0U) {
|
||||
query << ", ";
|
||||
}
|
||||
|
||||
query << "\"" << ctx_->group_by.at(idx) << "\"";
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx_->order_by.has_value()) {
|
||||
query << " ORDER BY \"" << ctx_->order_by.value().first << "\" ";
|
||||
query << (ctx_->order_by.value().second ? "ASC" : "DESC");
|
||||
}
|
||||
|
||||
if (ctx_->limit.has_value()) {
|
||||
query << " LIMIT " << ctx_->limit.value();
|
||||
}
|
||||
|
||||
if (ctx_->offset.has_value()) {
|
||||
query << " OFFSET " << ctx_->offset.value();
|
||||
}
|
||||
|
||||
query << ';';
|
||||
|
||||
return query.str();
|
||||
}
|
||||
|
||||
auto db_select::go() const -> db_result {
|
||||
sqlite3_stmt *stmt_ptr{nullptr};
|
||||
auto query_str = dump();
|
||||
auto res =
|
||||
sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr);
|
||||
|
||||
auto stmt = db3_stmt_t{
|
||||
stmt_ptr,
|
||||
sqlite3_statement_deleter(),
|
||||
};
|
||||
|
||||
if (res != SQLITE_OK) {
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
|
||||
if (not ctx_->where_data) {
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
|
||||
for (std::int32_t idx = 0;
|
||||
idx < static_cast<std::int32_t>(ctx_->where_data->values.size());
|
||||
idx++) {
|
||||
res =
|
||||
std::visit(overloaded{
|
||||
[&idx, &stmt](std::int64_t data) -> std::int32_t {
|
||||
return sqlite3_bind_int64(stmt.get(), idx + 1, data);
|
||||
},
|
||||
[&idx, &stmt](const std::string &data) -> std::int32_t {
|
||||
return sqlite3_bind_text(stmt.get(), idx + 1,
|
||||
data.c_str(), -1, nullptr);
|
||||
},
|
||||
},
|
||||
ctx_->where_data->values.at(static_cast<std::size_t>(idx)));
|
||||
if (res != SQLITE_OK) {
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
}
|
||||
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
|
||||
auto db_select::group(context::w_t::group_func_t func) -> context::w_t::wn_t {
|
||||
if (not ctx_->where_data) {
|
||||
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
|
||||
context::w_t{0U, ctx_},
|
||||
{},
|
||||
{},
|
||||
});
|
||||
}
|
||||
|
||||
return ctx_->where_data->base.group(std::move(func));
|
||||
}
|
||||
|
||||
auto db_select::group_by(std::string column_name) -> db_select {
|
||||
ctx_->group_by.emplace_back(std::move(column_name));
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_select::limit(std::int32_t value) -> db_select {
|
||||
ctx_->limit = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_select::offset(std::int32_t value) -> db_select {
|
||||
ctx_->offset = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_select::order_by(std::string column_name, bool ascending) -> db_select {
|
||||
ctx_->order_by = {column_name, ascending};
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_select::where(std::string column_name) const -> context::w_t::cn_t {
|
||||
if (not ctx_->where_data) {
|
||||
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
|
||||
context::w_t{0U, ctx_},
|
||||
{},
|
||||
{},
|
||||
});
|
||||
}
|
||||
|
||||
return ctx_->where_data->base.where(column_name);
|
||||
}
|
||||
} // namespace fifthgrid::utils::db::sqlite
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_SQLITE)
|
||||
189
support/src/utils/db/sqlite/db_update.cpp
Normal file
189
support/src/utils/db/sqlite/db_update.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(PROJECT_ENABLE_SQLITE)
|
||||
|
||||
#include "utils/db/sqlite/db_update.hpp"
|
||||
|
||||
namespace fifthgrid::utils::db::sqlite {
|
||||
auto db_update::context::db_update_op_t::dump() const -> std::string {
|
||||
return db_update{ctx}.dump();
|
||||
}
|
||||
|
||||
auto db_update::context::db_update_op_t::go() const -> db_result {
|
||||
return db_update{ctx}.go();
|
||||
}
|
||||
|
||||
auto db_update::context::db_update_op_t::limit(std::int32_t value)
|
||||
-> db_update::context::db_update_op_t {
|
||||
db_update{ctx}.limit(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_update::context::db_update_op_t::order_by(std::string column_name,
|
||||
bool ascending)
|
||||
-> db_update::context::db_update_op_t {
|
||||
db_update{ctx}.order_by(column_name, ascending);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_update::column_value(std::string column_name, db_types_t value)
|
||||
-> db_update {
|
||||
ctx_->column_values[column_name] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_update::dump() const -> std::string {
|
||||
std::stringstream query;
|
||||
query << "UPDATE \"" << ctx_->table_name << "\" SET ";
|
||||
|
||||
for (std::int32_t idx = 0;
|
||||
idx < static_cast<std::int32_t>(ctx_->column_values.size()); idx++) {
|
||||
if (idx > 0) {
|
||||
query << ", ";
|
||||
}
|
||||
|
||||
auto column = std::next(ctx_->column_values.begin(), idx);
|
||||
query << '"' << column->first << "\"=?" + std::to_string(idx + 1);
|
||||
}
|
||||
|
||||
if (ctx_->where_data) {
|
||||
auto idx{static_cast<std::int32_t>(ctx_->column_values.size())};
|
||||
query << " WHERE " << ctx_->where_data->base.dump(idx);
|
||||
}
|
||||
|
||||
if (ctx_->order_by.has_value()) {
|
||||
query << " ORDER BY \"" << ctx_->order_by.value().first << "\" ";
|
||||
query << (ctx_->order_by.value().second ? "ASC" : "DESC");
|
||||
}
|
||||
|
||||
if (ctx_->limit.has_value()) {
|
||||
query << " LIMIT " << ctx_->limit.value();
|
||||
}
|
||||
|
||||
query << ';';
|
||||
|
||||
return query.str();
|
||||
}
|
||||
|
||||
auto db_update::go() const -> db_result {
|
||||
sqlite3_stmt *stmt_ptr{nullptr};
|
||||
|
||||
auto query_str = dump();
|
||||
auto res =
|
||||
sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr);
|
||||
|
||||
auto stmt = db3_stmt_t{
|
||||
stmt_ptr,
|
||||
sqlite3_statement_deleter(),
|
||||
};
|
||||
|
||||
if (res != SQLITE_OK) {
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
|
||||
for (std::int32_t idx = 0;
|
||||
idx < static_cast<std::int32_t>(ctx_->column_values.size()); idx++) {
|
||||
res =
|
||||
std::visit(overloaded{
|
||||
[&idx, &stmt](std::int64_t data) -> std::int32_t {
|
||||
return sqlite3_bind_int64(stmt.get(), idx + 1, data);
|
||||
},
|
||||
[&idx, &stmt](const std::string &data) -> std::int32_t {
|
||||
return sqlite3_bind_text(stmt.get(), idx + 1,
|
||||
data.c_str(), -1, nullptr);
|
||||
},
|
||||
},
|
||||
std::next(ctx_->column_values.begin(), idx)->second);
|
||||
if (res != SQLITE_OK) {
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
}
|
||||
|
||||
if (not ctx_->where_data) {
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
|
||||
for (std::int32_t idx = 0;
|
||||
idx < static_cast<std::int32_t>(ctx_->where_data->values.size());
|
||||
idx++) {
|
||||
res = std::visit(
|
||||
overloaded{
|
||||
[this, &idx, &stmt](std::int64_t data) -> std::int32_t {
|
||||
return sqlite3_bind_int64(
|
||||
stmt.get(),
|
||||
idx + static_cast<std::int32_t>(ctx_->column_values.size()) +
|
||||
1,
|
||||
data);
|
||||
},
|
||||
[this, &idx, &stmt](const std::string &data) -> std::int32_t {
|
||||
return sqlite3_bind_text(
|
||||
stmt.get(),
|
||||
idx + static_cast<std::int32_t>(ctx_->column_values.size()) +
|
||||
1,
|
||||
data.c_str(), -1, nullptr);
|
||||
},
|
||||
},
|
||||
ctx_->where_data->values.at(static_cast<std::size_t>(idx)));
|
||||
if (res != SQLITE_OK) {
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
}
|
||||
|
||||
return {std::move(stmt), res};
|
||||
}
|
||||
|
||||
auto db_update::group(context::w_t::group_func_t func) -> context::w_t::wn_t {
|
||||
if (not ctx_->where_data) {
|
||||
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
|
||||
context::w_t{0U, ctx_},
|
||||
{},
|
||||
{},
|
||||
});
|
||||
}
|
||||
|
||||
return ctx_->where_data->base.group(std::move(func));
|
||||
}
|
||||
|
||||
auto db_update::limit(std::int32_t value) -> db_update {
|
||||
ctx_->limit = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_update::order_by(std::string column_name, bool ascending) -> db_update {
|
||||
ctx_->order_by = {column_name, ascending};
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto db_update::where(std::string column_name) const -> context::w_t::cn_t {
|
||||
if (not ctx_->where_data) {
|
||||
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
|
||||
context::w_t{0U, ctx_},
|
||||
{},
|
||||
{},
|
||||
});
|
||||
}
|
||||
|
||||
return ctx_->where_data->base.where(column_name);
|
||||
}
|
||||
} // namespace fifthgrid::utils::db::sqlite
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_SQLITE)
|
||||
52
support/src/utils/directory.cpp
Normal file
52
support/src/utils/directory.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "utils/file.hpp"
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#if defined(_WIN32)
|
||||
#include "utils/path.hpp"
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
namespace fifthgrid::utils::directory {
|
||||
auto temp() -> std::string {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
#if defined(_WIN32)
|
||||
auto ret{utils::get_environment_variable("TEMP")};
|
||||
if (ret.empty()) {
|
||||
ret = utils::path::combine(utils::get_environment_variable("LOCALAPPDATA"),
|
||||
{"Temp"});
|
||||
}
|
||||
#else // !defined(_WIN32)
|
||||
std::string ret{"/tmp"};
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
if (not utils::file::directory{ret}.create_directory()) {
|
||||
utils::error::handle_error(function_name,
|
||||
utils::error::create_error_message(
|
||||
{"failed to create directory", ret}));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
} // namespace fifthgrid::utils::directory
|
||||
701
support/src/utils/encrypting_reader.cpp
Normal file
701
support/src/utils/encrypting_reader.cpp
Normal file
@@ -0,0 +1,701 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
|
||||
#include "utils/base64.hpp"
|
||||
#include "utils/collection.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/config.hpp"
|
||||
#include "utils/encryption.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/unix.hpp"
|
||||
#include "utils/windows.hpp"
|
||||
|
||||
#if !defined(CURL_READFUNC_ABORT)
|
||||
#define CURL_READFUNC_ABORT (-1)
|
||||
#endif // !defined(CURL_READFUNC_ABORT)
|
||||
|
||||
namespace fifthgrid::utils::encryption {
|
||||
class encrypting_streambuf final : public encrypting_reader::streambuf {
|
||||
public:
|
||||
encrypting_streambuf(const encrypting_streambuf &) = default;
|
||||
encrypting_streambuf(encrypting_streambuf &&) = delete;
|
||||
auto operator=(const encrypting_streambuf &)
|
||||
-> encrypting_streambuf & = delete;
|
||||
auto operator=(encrypting_streambuf &&) -> encrypting_streambuf & = delete;
|
||||
|
||||
explicit encrypting_streambuf(const encrypting_reader &reader)
|
||||
: reader_(reader) {
|
||||
setg(reinterpret_cast<char *>(0), reinterpret_cast<char *>(0),
|
||||
reinterpret_cast<char *>(reader_.get_total_size()));
|
||||
}
|
||||
|
||||
~encrypting_streambuf() override = default;
|
||||
|
||||
private:
|
||||
encrypting_reader reader_;
|
||||
|
||||
protected:
|
||||
auto seekoff(off_type off, std::ios_base::seekdir dir,
|
||||
std::ios_base::openmode which = std::ios_base::out |
|
||||
std::ios_base::in)
|
||||
-> pos_type override {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if ((which & std::ios_base::in) != std::ios_base::in) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"output is not supported",
|
||||
});
|
||||
}
|
||||
|
||||
const auto set_position = [this](char *next) -> pos_type {
|
||||
if ((next <= egptr()) && (next >= eback())) {
|
||||
setg(eback(), next, reinterpret_cast<char *>(reader_.get_total_size()));
|
||||
return static_cast<std::streamoff>(
|
||||
reinterpret_cast<std::uintptr_t>(gptr()));
|
||||
}
|
||||
|
||||
return {traits_type::eof()};
|
||||
};
|
||||
|
||||
switch (dir) {
|
||||
case std::ios_base::beg:
|
||||
return set_position(eback() + off);
|
||||
|
||||
case std::ios_base::cur:
|
||||
return set_position(gptr() + off);
|
||||
|
||||
case std::ios_base::end:
|
||||
return set_position(egptr() + off);
|
||||
}
|
||||
|
||||
return encrypting_reader::streambuf::seekoff(off, dir, which);
|
||||
}
|
||||
|
||||
auto seekpos(pos_type pos,
|
||||
std::ios_base::openmode which = std::ios_base::out |
|
||||
std::ios_base::in)
|
||||
-> pos_type override {
|
||||
return seekoff(pos, std::ios_base::beg, which);
|
||||
}
|
||||
|
||||
auto uflow() -> int_type override {
|
||||
auto ret = underflow();
|
||||
if (ret == traits_type::eof()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
gbump(1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto underflow() -> int_type override {
|
||||
if (gptr() == egptr()) {
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
reader_.set_read_position(reinterpret_cast<std::uintptr_t>(gptr()));
|
||||
|
||||
char c{};
|
||||
auto res = encrypting_reader::reader_function(&c, 1U, 1U, &reader_);
|
||||
if (res != 1) {
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
auto xsgetn(char *ptr, std::streamsize count) -> std::streamsize override {
|
||||
if (gptr() == egptr()) {
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
reader_.set_read_position(reinterpret_cast<std::uintptr_t>(gptr()));
|
||||
|
||||
auto res = encrypting_reader::reader_function(
|
||||
ptr, 1U, static_cast<std::size_t>(count), &reader_);
|
||||
if ((res == reader_.get_error_return()) ||
|
||||
(reader_.get_stop_requested() &&
|
||||
(res == static_cast<std::size_t>(CURL_READFUNC_ABORT)))) {
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
setg(eback(), gptr() + res,
|
||||
reinterpret_cast<char *>(reader_.get_total_size()));
|
||||
return static_cast<std::streamsize>(res);
|
||||
}
|
||||
};
|
||||
|
||||
class encrypting_reader_iostream final : public encrypting_reader::iostream {
|
||||
public:
|
||||
encrypting_reader_iostream(const encrypting_reader_iostream &) = delete;
|
||||
encrypting_reader_iostream(encrypting_reader_iostream &&) = delete;
|
||||
|
||||
auto operator=(const encrypting_reader_iostream &)
|
||||
-> encrypting_reader_iostream & = delete;
|
||||
auto operator=(encrypting_reader_iostream &&)
|
||||
-> encrypting_reader_iostream & = delete;
|
||||
|
||||
explicit encrypting_reader_iostream(
|
||||
std::unique_ptr<encrypting_streambuf> buffer)
|
||||
: encrypting_reader::iostream(buffer.get()), buffer_(std::move(buffer)) {}
|
||||
|
||||
~encrypting_reader_iostream() override = default;
|
||||
|
||||
private:
|
||||
std::unique_ptr<encrypting_streambuf> buffer_;
|
||||
};
|
||||
|
||||
const std::size_t encrypting_reader::header_size_ = ([]() {
|
||||
return crypto_aead_xchacha20poly1305_IETF_NPUBBYTES +
|
||||
crypto_aead_xchacha20poly1305_IETF_ABYTES;
|
||||
})();
|
||||
const std::size_t encrypting_reader::data_chunk_size_ = (8UL * 1024UL * 1024UL);
|
||||
const std::size_t encrypting_reader::encrypted_chunk_size_ =
|
||||
data_chunk_size_ + header_size_;
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb, std::string_view token,
|
||||
std::optional<std::string> relative_parent_path, std::size_t error_return)
|
||||
: keys_(utils::encryption::generate_key<utils::hash::hash_256_t>(token),
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(token)),
|
||||
stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
common_initialize(true);
|
||||
create_encrypted_paths(file_name, relative_parent_path);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path,
|
||||
std::string_view source_path,
|
||||
std::string_view token,
|
||||
std::size_t error_return)
|
||||
: keys_(utils::encryption::generate_key<utils::hash::hash_256_t>(token),
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(token)),
|
||||
stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path) {
|
||||
common_initialize(true);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
|
||||
std::string_view source_path, std::string_view token,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return)
|
||||
: keys_(utils::encryption::generate_key<utils::hash::hash_256_t>(token),
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(token)),
|
||||
stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path),
|
||||
iv_list_(std::move(iv_list)) {
|
||||
common_initialize(false);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb, std::string_view token,
|
||||
kdf_config cfg, std::optional<std::string> relative_parent_path,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
common_initialize_kdf_keys(token, cfg);
|
||||
common_initialize(true);
|
||||
create_encrypted_paths(file_name, relative_parent_path);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path,
|
||||
std::string_view source_path,
|
||||
std::string_view token, kdf_config cfg,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path) {
|
||||
common_initialize_kdf_keys(token, cfg);
|
||||
common_initialize(true);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
|
||||
std::string_view source_path, std::string_view token, kdf_config cfg,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path),
|
||||
iv_list_(std::move(iv_list)) {
|
||||
common_initialize_kdf_keys(token, cfg);
|
||||
common_initialize(false);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb,
|
||||
const utils::hash::hash_256_t &master_key, const kdf_config &cfg,
|
||||
std::optional<std::string> relative_parent_path, std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
common_initialize_kdf_data(cfg, master_key);
|
||||
auto [path_key, path_cfg] = cfg.create_subkey(
|
||||
kdf_context::path, utils::generate_secure_random<std::uint64_t>(),
|
||||
master_key);
|
||||
keys_.second = std::move(path_key);
|
||||
kdf_headers_->second = path_cfg.to_header();
|
||||
common_initialize(true);
|
||||
create_encrypted_paths(file_name, relative_parent_path);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb,
|
||||
const utils::hash::hash_256_t &master_key,
|
||||
const std::pair<kdf_config, kdf_config> &configs,
|
||||
std::optional<std::string> relative_parent_path, std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
keys_ = {
|
||||
configs.first.recreate_subkey(utils::encryption::kdf_context::data,
|
||||
master_key),
|
||||
configs.second.recreate_subkey(utils::encryption::kdf_context::path,
|
||||
master_key),
|
||||
};
|
||||
kdf_headers_ = {
|
||||
configs.first.to_header(),
|
||||
configs.second.to_header(),
|
||||
};
|
||||
common_initialize(true);
|
||||
create_encrypted_paths(file_name, relative_parent_path);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path,
|
||||
std::string_view source_path,
|
||||
const utils::hash::hash_256_t &master_key,
|
||||
const kdf_config &cfg,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path) {
|
||||
common_initialize_kdf_data(cfg, master_key);
|
||||
common_initialize_kdf_path(master_key);
|
||||
common_initialize(true);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
|
||||
std::string_view source_path, const utils::hash::hash_256_t &master_key,
|
||||
const kdf_config &cfg,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path),
|
||||
iv_list_(std::move(iv_list)) {
|
||||
common_initialize_kdf_data(cfg, master_key);
|
||||
common_initialize_kdf_path(master_key);
|
||||
common_initialize(false);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
|
||||
std::string_view source_path, const utils::hash::hash_256_t &master_key,
|
||||
const std::pair<kdf_config, kdf_config> &configs,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path),
|
||||
iv_list_(std::move(iv_list)) {
|
||||
keys_.first = configs.first.recreate_subkey(
|
||||
utils::encryption::kdf_context::data, master_key);
|
||||
keys_.second = configs.second.recreate_subkey(
|
||||
utils::encryption::kdf_context::path, master_key);
|
||||
kdf_headers_ = {
|
||||
configs.first.to_header(),
|
||||
configs.second.to_header(),
|
||||
};
|
||||
common_initialize(false);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(const encrypting_reader &reader)
|
||||
: keys_(reader.keys_),
|
||||
stop_requested_cb_(reader.stop_requested_cb_),
|
||||
error_return_(reader.error_return_),
|
||||
source_file_(
|
||||
utils::file::file::open_file(reader.source_file_->get_path(), true)),
|
||||
encrypted_file_name_(reader.encrypted_file_name_),
|
||||
encrypted_file_path_(reader.encrypted_file_path_),
|
||||
iv_list_(reader.iv_list_),
|
||||
chunk_buffers_(reader.chunk_buffers_),
|
||||
kdf_headers_(reader.kdf_headers_),
|
||||
last_data_chunk_(reader.last_data_chunk_),
|
||||
last_data_chunk_size_(reader.last_data_chunk_size_),
|
||||
read_offset_(reader.read_offset_),
|
||||
total_size_(reader.total_size_) {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not *source_file_) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"file open failed",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
auto encrypting_reader::calculate_decrypted_size(std::uint64_t total_size,
|
||||
bool uses_kdf)
|
||||
-> std::uint64_t {
|
||||
if (uses_kdf) {
|
||||
total_size -= kdf_config::size();
|
||||
}
|
||||
|
||||
return total_size - (utils::divide_with_ceiling(
|
||||
total_size, static_cast<std::uint64_t>(
|
||||
get_encrypted_chunk_size())) *
|
||||
encryption_header_size);
|
||||
}
|
||||
|
||||
auto encrypting_reader::calculate_encrypted_size(std::string_view source_path,
|
||||
bool uses_kdf)
|
||||
-> std::uint64_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
auto opt_size = utils::file::file{source_path}.size();
|
||||
if (not opt_size.has_value()) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"get file size failed",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
source_path,
|
||||
});
|
||||
}
|
||||
|
||||
return calculate_encrypted_size(opt_size.value(), uses_kdf);
|
||||
}
|
||||
|
||||
auto encrypting_reader::calculate_encrypted_size(std::uint64_t size,
|
||||
bool uses_kdf)
|
||||
-> std::uint64_t {
|
||||
auto total_chunks = utils::divide_with_ceiling(
|
||||
size, static_cast<std::uint64_t>(data_chunk_size_));
|
||||
return size + (total_chunks * encryption_header_size) +
|
||||
(uses_kdf ? kdf_config::size() : 0U);
|
||||
}
|
||||
|
||||
void encrypting_reader::common_initialize(bool procces_iv_list) {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not *source_file_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"file open failed",
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
|
||||
auto opt_size = source_file_->size();
|
||||
if (not opt_size.has_value()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get file size",
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
auto file_size = opt_size.value();
|
||||
|
||||
auto total_chunks = utils::divide_with_ceiling(
|
||||
file_size, static_cast<std::uint64_t>(data_chunk_size_));
|
||||
total_size_ = file_size + (total_chunks * encryption_header_size) +
|
||||
(kdf_headers_.has_value() ? kdf_headers_->first.size() : 0U);
|
||||
last_data_chunk_ = total_chunks - 1U;
|
||||
last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size
|
||||
: (file_size % data_chunk_size_) == 0U
|
||||
? data_chunk_size_
|
||||
: file_size % data_chunk_size_;
|
||||
if (not procces_iv_list) {
|
||||
return;
|
||||
}
|
||||
|
||||
iv_list_.resize(total_chunks);
|
||||
for (auto &data : iv_list_) {
|
||||
randombytes_buf(data.data(), data.size());
|
||||
}
|
||||
}
|
||||
|
||||
void encrypting_reader::common_initialize_kdf_data(
|
||||
const kdf_config &cfg, const utils::hash::hash_256_t &master_key) {
|
||||
auto [data_key, data_cfg] = cfg.create_subkey(
|
||||
kdf_context::data, utils::generate_secure_random<std::uint64_t>(),
|
||||
master_key);
|
||||
keys_.first = std::move(data_key);
|
||||
kdf_headers_ = {data_cfg.to_header(), {}};
|
||||
}
|
||||
|
||||
void encrypting_reader::common_initialize_kdf_keys(std::string_view token,
|
||||
kdf_config &cfg) {
|
||||
auto key =
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
|
||||
keys_ = {key, key};
|
||||
kdf_headers_ = {cfg.to_header(), cfg.to_header()};
|
||||
}
|
||||
|
||||
void encrypting_reader::common_initialize_kdf_path(
|
||||
const utils::hash::hash_256_t &master_key) {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
auto buffer = macaron::Base64::Decode(encrypted_file_path_);
|
||||
|
||||
kdf_config path_cfg;
|
||||
if (not kdf_config::from_header(buffer, path_cfg)) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {"failed to create path kdf config from header"});
|
||||
}
|
||||
|
||||
utils::hash::hash_256_t path_key;
|
||||
std::tie(path_key, std::ignore) =
|
||||
path_cfg.create_subkey(kdf_context::path, path_cfg.unique_id, master_key);
|
||||
|
||||
kdf_headers_->second = path_cfg.to_header();
|
||||
}
|
||||
|
||||
void encrypting_reader::create_encrypted_paths(
|
||||
std::string_view file_name,
|
||||
std::optional<std::string> relative_parent_path) {
|
||||
data_buffer result;
|
||||
utils::encryption::encrypt_data(
|
||||
keys_.second, reinterpret_cast<const unsigned char *>(file_name.data()),
|
||||
file_name.size(), result);
|
||||
if (kdf_headers_.has_value()) {
|
||||
result.insert(result.begin(), kdf_headers_->second.begin(),
|
||||
kdf_headers_->second.end());
|
||||
}
|
||||
|
||||
encrypted_file_name_ =
|
||||
kdf_headers_.has_value()
|
||||
? macaron::Base64::EncodeUrlSafe(result.data(), result.size())
|
||||
: utils::collection::to_hex_string(result);
|
||||
|
||||
if (not relative_parent_path.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &part :
|
||||
utils::string::split(relative_parent_path.value(),
|
||||
utils::path::directory_seperator, false)) {
|
||||
utils::encryption::encrypt_data(
|
||||
keys_.second, reinterpret_cast<const unsigned char *>(part.c_str()),
|
||||
strnlen(part.c_str(), part.size()), result);
|
||||
if (kdf_headers_.has_value()) {
|
||||
result.insert(result.begin(), kdf_headers_->second.begin(),
|
||||
kdf_headers_->second.end());
|
||||
}
|
||||
|
||||
encrypted_file_path_ +=
|
||||
'/' +
|
||||
(kdf_headers_.has_value()
|
||||
? macaron::Base64::EncodeUrlSafe(result.data(), result.size())
|
||||
: utils::collection::to_hex_string(result));
|
||||
}
|
||||
|
||||
encrypted_file_path_ += '/' + encrypted_file_name_;
|
||||
}
|
||||
|
||||
auto encrypting_reader::create_iostream() const
|
||||
-> std::shared_ptr<encrypting_reader::iostream> {
|
||||
return std::make_shared<encrypting_reader_iostream>(
|
||||
std::make_unique<encrypting_streambuf>(*this));
|
||||
}
|
||||
|
||||
auto encrypting_reader::get_kdf_config_for_data() const
|
||||
-> std::optional<kdf_config> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not kdf_headers_.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
kdf_config cfg;
|
||||
if (not kdf_config::from_header(kdf_headers_->first, cfg)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"invalid kdf header",
|
||||
});
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
auto encrypting_reader::get_kdf_config_for_path() const
|
||||
-> std::optional<kdf_config> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not kdf_headers_.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
kdf_config cfg;
|
||||
if (not kdf_config::from_header(kdf_headers_->second, cfg)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"invalid kdf header",
|
||||
});
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
auto encrypting_reader::reader_function(char *buffer, size_t size,
|
||||
size_t nitems) -> size_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
auto read_size =
|
||||
static_cast<std::uint64_t>(size) * static_cast<std::uint64_t>(nitems);
|
||||
if (read_size == 0U) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
std::span<char> dest(buffer, read_size);
|
||||
auto read_offset{read_offset_};
|
||||
std::size_t total_read{};
|
||||
auto total_size{total_size_};
|
||||
|
||||
if (kdf_headers_.has_value()) {
|
||||
auto &hdr = kdf_headers_->first;
|
||||
total_size -= hdr.size();
|
||||
|
||||
if (read_offset < hdr.size()) {
|
||||
auto to_read{
|
||||
utils::calculate_read_size(hdr.size(), read_size, read_offset),
|
||||
};
|
||||
read_offset_ += to_read;
|
||||
|
||||
std::memcpy(&dest[total_read], &hdr.at(read_offset), to_read);
|
||||
if (read_size - to_read == 0) {
|
||||
return to_read;
|
||||
}
|
||||
|
||||
read_offset = 0U;
|
||||
read_size -= to_read;
|
||||
total_read += to_read;
|
||||
} else {
|
||||
read_offset -= hdr.size();
|
||||
}
|
||||
}
|
||||
|
||||
auto chunk = static_cast<std::size_t>(read_offset / encrypted_chunk_size_);
|
||||
auto chunk_offset =
|
||||
static_cast<std::size_t>(read_offset % encrypted_chunk_size_);
|
||||
auto remain = utils::calculate_read_size(total_size, read_size, read_offset);
|
||||
|
||||
auto ret{false};
|
||||
if (read_offset < total_size) {
|
||||
try {
|
||||
ret = true;
|
||||
while (not get_stop_requested() && ret && (remain != 0U)) {
|
||||
if (not chunk_buffers_.contains(chunk)) {
|
||||
auto &chunk_buffer = chunk_buffers_[chunk];
|
||||
data_buffer file_data(chunk == last_data_chunk_
|
||||
? last_data_chunk_size_
|
||||
: data_chunk_size_);
|
||||
chunk_buffer.resize(file_data.size() + encryption_header_size);
|
||||
|
||||
std::size_t bytes_read{};
|
||||
ret = source_file_->read(
|
||||
file_data,
|
||||
static_cast<std::uint64_t>(chunk) *
|
||||
static_cast<std::uint64_t>(data_chunk_size_),
|
||||
&bytes_read);
|
||||
if (ret) {
|
||||
utils::encryption::encrypt_data(iv_list_.at(chunk), keys_.first,
|
||||
file_data, chunk_buffer);
|
||||
}
|
||||
} else if (chunk != 0U) {
|
||||
chunk_buffers_.erase(chunk - 1U);
|
||||
}
|
||||
|
||||
auto &chunk_buffer = chunk_buffers_[chunk];
|
||||
auto to_read = std::min(chunk_buffer.size() - chunk_offset, remain);
|
||||
std::memcpy(&dest[total_read], &chunk_buffer[chunk_offset], to_read);
|
||||
total_read += to_read;
|
||||
remain -= to_read;
|
||||
chunk_offset = 0U;
|
||||
++chunk;
|
||||
read_offset_ += to_read;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
ret = false;
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
return get_stop_requested() ? static_cast<std::size_t>(CURL_READFUNC_ABORT)
|
||||
: ret ? total_read
|
||||
: error_return_;
|
||||
}
|
||||
} // namespace fifthgrid::utils::encryption
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
313
support/src/utils/encryption.cpp
Normal file
313
support/src/utils/encryption.cpp
Normal file
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
|
||||
#include "utils/encryption.hpp"
|
||||
|
||||
#include "utils/base64.hpp"
|
||||
#include "utils/collection.hpp"
|
||||
#include "utils/config.hpp"
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
#include "utils/hash.hpp"
|
||||
#include "utils/path.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr auto resize_by(fifthgrid::data_span &data, std::size_t /* size */)
|
||||
-> fifthgrid::data_span & {
|
||||
return data;
|
||||
}
|
||||
|
||||
auto resize_by(fifthgrid::data_buffer &data, std::size_t size)
|
||||
-> fifthgrid::data_buffer & {
|
||||
data.resize(data.size() + size);
|
||||
return data;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace fifthgrid::utils::encryption {
|
||||
auto kdf_config::to_header() const -> data_buffer {
|
||||
kdf_config tmp{*this};
|
||||
tmp.checksum = boost::endian::native_to_big(tmp.checksum);
|
||||
tmp.unique_id = boost::endian::native_to_big(tmp.unique_id);
|
||||
|
||||
data_buffer ret(size());
|
||||
std::memcpy(ret.data(), &tmp, ret.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto kdf_config::generate_checksum() const -> std::uint64_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
kdf_config tmp = *this;
|
||||
tmp.checksum = 0;
|
||||
|
||||
auto hash = utils::hash::create_hash_blake2b_64(tmp.to_header());
|
||||
std::uint64_t ret{};
|
||||
std::memcpy(&ret, hash.data(), hash.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto kdf_config::from_header(data_cspan data, kdf_config &cfg,
|
||||
bool ignore_checksum) -> bool {
|
||||
if (data.size() < kdf_config::size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(&cfg, data.data(), kdf_config::size());
|
||||
|
||||
cfg.checksum = boost::endian::big_to_native(cfg.checksum);
|
||||
cfg.unique_id = boost::endian::big_to_native(cfg.unique_id);
|
||||
return cfg.version == kdf_version::v1 && cfg.kdf == kdf_type::argon2id &&
|
||||
cfg.memlimit >= memlimit_level::level1 &&
|
||||
cfg.memlimit <= memlimit_level::level4 &&
|
||||
cfg.opslimit >= opslimit_level::level1 &&
|
||||
cfg.opslimit <= opslimit_level::level3 &&
|
||||
(ignore_checksum || cfg.checksum == cfg.generate_checksum());
|
||||
}
|
||||
|
||||
void kdf_config::seal() {
|
||||
randombytes_buf(salt.data(), salt.size());
|
||||
checksum = generate_checksum();
|
||||
}
|
||||
|
||||
auto decrypt_file_name(std::string_view encryption_token,
|
||||
std::string &file_name) -> bool {
|
||||
data_buffer buffer;
|
||||
if (not utils::collection::from_hex_string(file_name, buffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_name.clear();
|
||||
return utils::encryption::decrypt_data(encryption_token, buffer, file_name);
|
||||
}
|
||||
|
||||
auto decrypt_file_name(std::string_view encryption_token, const kdf_config &cfg,
|
||||
std::string &file_name) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto buffer = macaron::Base64::Decode(file_name);
|
||||
|
||||
file_name.clear();
|
||||
return utils::encryption::decrypt_data(encryption_token, cfg, buffer,
|
||||
file_name);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto decrypt_file_name(const utils::hash::hash_256_t &master_key,
|
||||
std::string &file_name) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto buffer = macaron::Base64::Decode(file_name);
|
||||
|
||||
utils::encryption::kdf_config path_cfg;
|
||||
if (not utils::encryption::kdf_config::from_header(buffer, path_cfg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto path_key = path_cfg.recreate_subkey(
|
||||
utils::encryption::kdf_context::path, master_key);
|
||||
|
||||
file_name.clear();
|
||||
return utils::encryption::decrypt_data(
|
||||
path_key, &buffer[utils::encryption::kdf_config::size()],
|
||||
buffer.size() - utils::encryption::kdf_config::size(), file_name);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto decrypt_file_path(std::string_view encryption_token,
|
||||
std::string &file_path) -> bool {
|
||||
std::vector<std::string> decrypted_parts;
|
||||
for (const auto &part : std::filesystem::path(file_path)) {
|
||||
auto file_name = part.string();
|
||||
if (file_name == "/") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not decrypt_file_name(encryption_token, file_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
decrypted_parts.push_back(file_name);
|
||||
}
|
||||
|
||||
file_path =
|
||||
utils::path::create_api_path(utils::string::join(decrypted_parts, '/'));
|
||||
return true;
|
||||
}
|
||||
|
||||
auto decrypt_file_path(std::string_view encryption_token, const kdf_config &cfg,
|
||||
std::string &file_path) -> bool {
|
||||
std::vector<std::string> decrypted_parts;
|
||||
for (const auto &part : std::filesystem::path(file_path)) {
|
||||
auto file_name = part.string();
|
||||
if (file_name == "/") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not decrypt_file_name(encryption_token, cfg, file_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
decrypted_parts.push_back(file_name);
|
||||
}
|
||||
|
||||
file_path =
|
||||
utils::path::create_api_path(utils::string::join(decrypted_parts, '/'));
|
||||
return true;
|
||||
}
|
||||
|
||||
auto decrypt_file_path(const utils::hash::hash_256_t &master_key,
|
||||
std::string &file_path) -> bool {
|
||||
std::vector<std::string> decrypted_parts;
|
||||
for (const auto &part : std::filesystem::path(file_path)) {
|
||||
auto file_name = part.string();
|
||||
if (file_name == "/") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not decrypt_file_name(master_key, file_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
decrypted_parts.push_back(file_name);
|
||||
}
|
||||
|
||||
file_path =
|
||||
utils::path::create_api_path(utils::string::join(decrypted_parts, '/'));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename data_t>
|
||||
[[nodiscard]] auto
|
||||
read_encrypted_range(http_range range, const utils::hash::hash_256_t &key,
|
||||
reader_func_t reader_func, std::uint64_t total_size,
|
||||
data_t &data, std::uint8_t file_header_size,
|
||||
std::size_t &bytes_read) -> bool {
|
||||
bytes_read = 0U;
|
||||
|
||||
{
|
||||
if (total_size == 0U) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint64_t begin = range.begin;
|
||||
std::uint64_t end = range.end;
|
||||
|
||||
if (begin >= total_size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint64_t last = total_size - 1U;
|
||||
if (end > last) {
|
||||
end = last;
|
||||
}
|
||||
|
||||
if (end < begin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
range = http_range{
|
||||
.begin = begin,
|
||||
.end = end,
|
||||
};
|
||||
}
|
||||
|
||||
auto encrypted_chunk_size =
|
||||
utils::encryption::encrypting_reader::get_encrypted_chunk_size();
|
||||
auto data_chunk_size =
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size();
|
||||
|
||||
auto start_chunk = static_cast<std::size_t>(range.begin / data_chunk_size);
|
||||
auto end_chunk = static_cast<std::size_t>(range.end / data_chunk_size);
|
||||
auto remain = range.end - range.begin + 1U;
|
||||
auto source_offset = static_cast<std::size_t>(range.begin % data_chunk_size);
|
||||
|
||||
for (std::size_t chunk = start_chunk; chunk <= end_chunk; chunk++) {
|
||||
data_buffer cipher;
|
||||
auto start_offset = (chunk * encrypted_chunk_size) + file_header_size;
|
||||
auto end_offset = std::min(
|
||||
start_offset + (total_size - (chunk * data_chunk_size)) +
|
||||
encryption_header_size - 1U,
|
||||
static_cast<std::uint64_t>(start_offset + encrypted_chunk_size - 1U));
|
||||
|
||||
if (not reader_func(cipher, start_offset, end_offset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data_buffer source_buffer;
|
||||
if (not utils::encryption::decrypt_data(key, cipher, source_buffer)) {
|
||||
return false;
|
||||
}
|
||||
cipher.clear();
|
||||
|
||||
auto data_size = static_cast<std::size_t>(std::min(
|
||||
remain, static_cast<std::uint64_t>(data_chunk_size - source_offset)));
|
||||
std::copy(std::next(source_buffer.begin(),
|
||||
static_cast<std::int64_t>(source_offset)),
|
||||
std::next(source_buffer.begin(),
|
||||
static_cast<std::int64_t>(source_offset + data_size)),
|
||||
std::next(resize_by(data, data_size).begin(),
|
||||
static_cast<std::int64_t>(bytes_read)));
|
||||
remain -= data_size;
|
||||
bytes_read += data_size;
|
||||
source_offset = 0U;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto read_encrypted_range(const http_range &range,
|
||||
const utils::hash::hash_256_t &key, bool uses_kdf,
|
||||
reader_func_t reader_func, std::uint64_t total_size,
|
||||
data_buffer &data) -> bool {
|
||||
std::size_t bytes_read{};
|
||||
return read_encrypted_range<data_buffer>(
|
||||
range, key, reader_func, total_size, data,
|
||||
uses_kdf ? kdf_config::size() : 0U, bytes_read);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto read_encrypted_range(
|
||||
const http_range &range, const utils::hash::hash_256_t &key, bool uses_kdf,
|
||||
reader_func_t reader_func, std::uint64_t total_size, unsigned char *data,
|
||||
std::size_t size, std::size_t &bytes_read) -> bool {
|
||||
data_span dest_buffer(data, size);
|
||||
return read_encrypted_range<data_span>(
|
||||
range, key, reader_func, total_size, dest_buffer,
|
||||
uses_kdf ? kdf_config::size() : 0U, bytes_read);
|
||||
}
|
||||
} // namespace fifthgrid::utils::encryption
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined (PROJECT_ENABLE_BOOST)
|
||||
50
support/src/utils/error.cpp
Normal file
50
support/src/utils/error.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "utils/error.hpp"
|
||||
|
||||
namespace fifthgrid::utils::error {
|
||||
auto create_error_message(std::vector<std::string_view> items) -> std::string {
|
||||
std::stringstream stream{};
|
||||
for (std::size_t idx = 0U; idx < items.size(); ++idx) {
|
||||
if (idx > 0) {
|
||||
stream << '|';
|
||||
}
|
||||
|
||||
stream << items.at(idx);
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
auto create_error_message(std::string_view function_name,
|
||||
std::vector<std::string_view> items) -> std::string {
|
||||
items.insert(items.begin(), function_name);
|
||||
return create_error_message(items);
|
||||
}
|
||||
|
||||
auto create_exception(std::string_view function_name,
|
||||
std::vector<std::string_view> items)
|
||||
-> std::runtime_error {
|
||||
return std::runtime_error(create_error_message(function_name, items));
|
||||
}
|
||||
|
||||
} // namespace fifthgrid::utils::error
|
||||
323
support/src/utils/error_handler.cpp
Normal file
323
support/src/utils/error_handler.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "utils/error.hpp"
|
||||
|
||||
#include "utils/config.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace fifthgrid::utils::error {
|
||||
std::atomic<const i_exception_handler *> exception_handler{
|
||||
&default_exception_handler,
|
||||
};
|
||||
|
||||
#if defined(PROJECT_ENABLE_V2_ERRORS)
|
||||
void iostream_exception_handler::handle_debug(std::string_view function_name,
|
||||
std::string_view msg) const {
|
||||
std::cout << create_error_message({
|
||||
"debug",
|
||||
function_name,
|
||||
msg,
|
||||
})
|
||||
<< std::endl;
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_V2_ERRORS)
|
||||
|
||||
void iostream_exception_handler::handle_error(std::string_view function_name,
|
||||
std::string_view msg) const {
|
||||
std::cerr << create_error_message({
|
||||
"error",
|
||||
function_name,
|
||||
msg,
|
||||
})
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void iostream_exception_handler::handle_exception(
|
||||
std::string_view function_name) const {
|
||||
std::cerr << create_error_message({
|
||||
"error",
|
||||
function_name,
|
||||
"exception",
|
||||
"unknown",
|
||||
})
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void iostream_exception_handler::handle_exception(
|
||||
std::string_view function_name, const std::exception &ex) const {
|
||||
std::cerr << create_error_message({
|
||||
"error",
|
||||
function_name,
|
||||
"exception",
|
||||
(ex.what() == nullptr ? "unknown" : ex.what()),
|
||||
})
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_V2_ERRORS)
|
||||
void iostream_exception_handler::handle_info(std::string_view function_name,
|
||||
std::string_view msg) const {
|
||||
std::cout << create_error_message({
|
||||
"info",
|
||||
function_name,
|
||||
msg,
|
||||
})
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void iostream_exception_handler::handle_trace(std::string_view function_name,
|
||||
std::string_view msg) const {
|
||||
std::cout << create_error_message({
|
||||
"trace",
|
||||
function_name,
|
||||
msg,
|
||||
})
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void iostream_exception_handler::handle_warn(std::string_view function_name,
|
||||
std::string_view msg) const {
|
||||
std::cout << create_error_message({
|
||||
"warn",
|
||||
function_name,
|
||||
msg,
|
||||
})
|
||||
<< std::endl;
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_V2_ERRORS)
|
||||
|
||||
#if defined(PROJECT_ENABLE_SPDLOG) && defined(PROJECT_ENABLE_V2_ERRORS)
|
||||
void spdlog_exception_handler::handle_debug(std::string_view function_name,
|
||||
std::string_view msg) const {
|
||||
auto console = spdlog::get("console");
|
||||
if (console) {
|
||||
console->debug(utils::error::create_error_message(function_name, {msg}));
|
||||
} else {
|
||||
fallback.handle_debug(function_name, msg);
|
||||
}
|
||||
|
||||
auto file = spdlog::get("file");
|
||||
if (not file) {
|
||||
return;
|
||||
}
|
||||
|
||||
file->debug(utils::error::create_error_message(function_name, {msg}));
|
||||
}
|
||||
|
||||
void spdlog_exception_handler::handle_error(std::string_view function_name,
|
||||
std::string_view msg) const {
|
||||
auto console = spdlog::get("console");
|
||||
if (console) {
|
||||
console->error(utils::error::create_error_message(function_name, {msg}));
|
||||
} else {
|
||||
fallback.handle_error(function_name, msg);
|
||||
}
|
||||
|
||||
auto file = spdlog::get("file");
|
||||
if (not file) {
|
||||
return;
|
||||
}
|
||||
|
||||
file->error(utils::error::create_error_message(function_name, {msg}));
|
||||
}
|
||||
|
||||
void spdlog_exception_handler::handle_exception(
|
||||
std::string_view function_name) const {
|
||||
auto console = spdlog::get("console");
|
||||
if (console) {
|
||||
console->error(utils::error::create_error_message(function_name,
|
||||
{
|
||||
"exception",
|
||||
"unknown exception",
|
||||
}));
|
||||
} else {
|
||||
fallback.handle_exception(function_name);
|
||||
}
|
||||
|
||||
auto file = spdlog::get("file");
|
||||
if (not file) {
|
||||
return;
|
||||
}
|
||||
|
||||
file->error(
|
||||
utils::error::create_error_message(function_name, {
|
||||
"exception",
|
||||
"unknown exception",
|
||||
}));
|
||||
}
|
||||
|
||||
void spdlog_exception_handler::handle_exception(
|
||||
std::string_view function_name, const std::exception &ex) const {
|
||||
auto console = spdlog::get("console");
|
||||
if (console) {
|
||||
console->error(utils::error::create_error_message(
|
||||
function_name, {
|
||||
"exception",
|
||||
(ex.what() == nullptr ? "unknown" : ex.what()),
|
||||
}));
|
||||
} else {
|
||||
fallback.handle_exception(function_name, ex);
|
||||
}
|
||||
|
||||
auto file = spdlog::get("file");
|
||||
if (not file) {
|
||||
return;
|
||||
}
|
||||
|
||||
file->error(utils::error::create_error_message(
|
||||
function_name, {
|
||||
"exception",
|
||||
(ex.what() == nullptr ? "unknown" : ex.what()),
|
||||
}));
|
||||
}
|
||||
|
||||
void spdlog_exception_handler::handle_info(std::string_view function_name,
|
||||
std::string_view msg) const {
|
||||
auto console = spdlog::get("console");
|
||||
if (console) {
|
||||
console->info(utils::error::create_error_message(function_name, {msg}));
|
||||
} else {
|
||||
fallback.handle_info(function_name, msg);
|
||||
}
|
||||
|
||||
auto file = spdlog::get("file");
|
||||
if (not file) {
|
||||
return;
|
||||
}
|
||||
|
||||
file->info(utils::error::create_error_message(function_name, {msg}));
|
||||
}
|
||||
|
||||
void spdlog_exception_handler::handle_trace(std::string_view function_name,
|
||||
std::string_view msg) const {
|
||||
auto console = spdlog::get("console");
|
||||
if (console) {
|
||||
console->trace(utils::error::create_error_message(function_name, {msg}));
|
||||
} else {
|
||||
fallback.handle_trace(function_name, msg);
|
||||
}
|
||||
|
||||
auto file = spdlog::get("file");
|
||||
if (not file) {
|
||||
return;
|
||||
}
|
||||
|
||||
file->trace(utils::error::create_error_message(function_name, {msg}));
|
||||
}
|
||||
|
||||
void spdlog_exception_handler::handle_warn(std::string_view function_name,
|
||||
std::string_view msg) const {
|
||||
auto console = spdlog::get("console");
|
||||
if (console) {
|
||||
console->warn(utils::error::create_error_message(function_name, {msg}));
|
||||
} else {
|
||||
fallback.handle_warn(function_name, msg);
|
||||
}
|
||||
|
||||
auto file = spdlog::get("file");
|
||||
if (not file) {
|
||||
return;
|
||||
}
|
||||
|
||||
file->warn(utils::error::create_error_message(function_name, {msg}));
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_SPDLOG) && defined(PROJECT_ENABLE_V2_ERRORS)
|
||||
|
||||
#if defined(PROJECT_ENABLE_V2_ERRORS)
|
||||
void handle_debug(std::string_view function_name, std::string_view msg) {
|
||||
const i_exception_handler *handler{exception_handler};
|
||||
if (handler != nullptr) {
|
||||
handler->handle_debug(function_name, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
default_exception_handler.handle_debug(function_name, msg);
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_V2_ERRORS)
|
||||
|
||||
void handle_error(std::string_view function_name, std::string_view msg) {
|
||||
const i_exception_handler *handler{exception_handler};
|
||||
if (handler != nullptr) {
|
||||
handler->handle_error(function_name, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
default_exception_handler.handle_error(function_name, msg);
|
||||
}
|
||||
|
||||
void handle_exception(std::string_view function_name) {
|
||||
const i_exception_handler *handler{exception_handler};
|
||||
if (handler != nullptr) {
|
||||
handler->handle_exception(function_name);
|
||||
return;
|
||||
}
|
||||
|
||||
default_exception_handler.handle_exception(function_name);
|
||||
}
|
||||
|
||||
void handle_exception(std::string_view function_name,
|
||||
const std::exception &ex) {
|
||||
const i_exception_handler *handler{exception_handler};
|
||||
if (handler != nullptr) {
|
||||
handler->handle_exception(function_name, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
default_exception_handler.handle_exception(function_name, ex);
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_V2_ERRORS)
|
||||
void handle_info(std::string_view function_name, std::string_view msg) {
|
||||
const i_exception_handler *handler{exception_handler};
|
||||
if (handler != nullptr) {
|
||||
handler->handle_info(function_name, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
default_exception_handler.handle_info(function_name, msg);
|
||||
}
|
||||
|
||||
void handle_trace(std::string_view function_name, std::string_view msg) {
|
||||
const i_exception_handler *handler{exception_handler};
|
||||
if (handler != nullptr) {
|
||||
handler->handle_trace(function_name, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
default_exception_handler.handle_trace(function_name, msg);
|
||||
}
|
||||
|
||||
void handle_warn(std::string_view function_name, std::string_view msg) {
|
||||
const i_exception_handler *handler{exception_handler};
|
||||
if (handler != nullptr) {
|
||||
handler->handle_warn(function_name, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
default_exception_handler.handle_warn(function_name, msg);
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_V2_ERRORS)
|
||||
|
||||
void set_exception_handler(const i_exception_handler *handler) {
|
||||
exception_handler = handler;
|
||||
}
|
||||
} // namespace fifthgrid::utils::error
|
||||
744
support/src/utils/file.cpp
Normal file
744
support/src/utils/file.cpp
Normal file
@@ -0,0 +1,744 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "utils/file.hpp"
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/encryption.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/path.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/time.hpp"
|
||||
#include "utils/unix.hpp"
|
||||
#include "utils/windows.hpp"
|
||||
|
||||
namespace fifthgrid::utils::file {
|
||||
auto change_to_process_directory() -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
#if defined(_WIN32)
|
||||
std::string file_name;
|
||||
file_name.resize(fifthgrid::max_path_length + 1U);
|
||||
|
||||
::GetModuleFileNameA(nullptr, file_name.data(),
|
||||
static_cast<DWORD>(file_name.size() - 1U));
|
||||
auto path = utils::path::get_parent_path(file_name.c_str());
|
||||
auto res = ::SetCurrentDirectoryA(path.c_str()) != 0;
|
||||
if (not res) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to set current directory",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
#else // !defined(_WIN32)
|
||||
std::string path;
|
||||
path.resize(fifthgrid::max_path_length + 1);
|
||||
#if defined(__APPLE__)
|
||||
auto res = proc_pidpath(getpid(), reinterpret_cast<void *>(path.data()),
|
||||
static_cast<uint32_t>(path.size()));
|
||||
path = path.c_str();
|
||||
#else // !defined(__APPLE__)
|
||||
auto res = readlink("/proc/self/exe", path.data(), path.size());
|
||||
if (res == -1) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to readlink",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
#endif // defined(__APPLE__)
|
||||
path = utils::path::get_parent_path(path);
|
||||
res = chdir(path.c_str());
|
||||
if (res != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to chdir",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto create_temp_name(std::string_view file_part) -> std::string {
|
||||
std::array<std::uint8_t, 8U> data{
|
||||
utils::generate_random_between<std::uint8_t>(0U, 9U),
|
||||
utils::generate_random_between<std::uint8_t>(0U, 9U),
|
||||
utils::generate_random_between<std::uint8_t>(0U, 9U),
|
||||
utils::generate_random_between<std::uint8_t>(0U, 9U),
|
||||
utils::generate_random_between<std::uint8_t>(0U, 9U),
|
||||
utils::generate_random_between<std::uint8_t>(0U, 9U),
|
||||
utils::generate_random_between<std::uint8_t>(0U, 9U),
|
||||
utils::generate_random_between<std::uint8_t>(0U, 9U),
|
||||
};
|
||||
|
||||
return std::accumulate(data.begin(), data.end(), std::string{file_part} + '_',
|
||||
[](auto &&name, auto &&val) -> auto {
|
||||
return name + std::to_string(val);
|
||||
});
|
||||
}
|
||||
|
||||
auto create_temp_name(std::wstring_view file_part) -> std::wstring {
|
||||
return utils::string::from_utf8(
|
||||
create_temp_name(utils::string::to_utf8(file_part)));
|
||||
}
|
||||
|
||||
auto get_free_drive_space(std::string_view path)
|
||||
-> std::optional<std::uint64_t> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
#if defined(_WIN32)
|
||||
ULARGE_INTEGER li{};
|
||||
if (not ::GetDiskFreeSpaceEx(std::string{path}.c_str(), &li, nullptr,
|
||||
nullptr)) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get free disk space",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
return li.QuadPart;
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
#if defined(__linux__)
|
||||
struct statfs64 u_stat{};
|
||||
if (statfs64(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get free disk space",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
return u_stat.f_bfree * static_cast<std::uint64_t>(u_stat.f_bsize);
|
||||
#endif // defined(__linux__)
|
||||
|
||||
#if defined(__APPLE__)
|
||||
struct statvfs u_stat{};
|
||||
if (statvfs(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get free disk space",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
return u_stat.f_bfree * static_cast<std::uint64_t>(u_stat.f_frsize);
|
||||
#endif // defined(__APPLE__)
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto get_free_drive_space(std::wstring_view path)
|
||||
-> std::optional<std::uint64_t> {
|
||||
return get_free_drive_space(utils::string::to_utf8(path));
|
||||
}
|
||||
|
||||
auto get_time(std::string_view path, time_type type)
|
||||
-> std::optional<std::uint64_t> {
|
||||
auto times = get_times(path);
|
||||
if (times.has_value()) {
|
||||
return times->get(type);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto get_time(std::wstring_view path, time_type type)
|
||||
-> std::optional<std::uint64_t> {
|
||||
return get_time(utils::string::to_utf8(path), type);
|
||||
}
|
||||
|
||||
auto get_times(std::string_view path) -> std::optional<file_times> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
file_times ret{};
|
||||
|
||||
#if defined(_WIN32)
|
||||
auto file_handle = ::CreateFileA(
|
||||
std::string{path}.c_str(), GENERIC_READ,
|
||||
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
|
||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (file_handle != INVALID_HANDLE_VALUE) {
|
||||
std::array<FILETIME, 3U> times{};
|
||||
auto res = ::GetFileTime(file_handle, ×.at(0U), ×.at(1U),
|
||||
×.at(2U));
|
||||
::CloseHandle(file_handle);
|
||||
if (res) {
|
||||
ret.accessed =
|
||||
utils::time::windows_file_time_to_unix_time(times.at(1U));
|
||||
ret.changed = utils::time::windows_file_time_to_unix_time(times.at(2U));
|
||||
ret.created = utils::time::windows_file_time_to_unix_time(times.at(0U));
|
||||
ret.modified =
|
||||
utils::time::windows_file_time_to_unix_time(times.at(2U));
|
||||
ret.written = utils::time::windows_file_time_to_unix_time(times.at(2U));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
struct _stat64 u_stat{};
|
||||
if (_stat64(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get file times",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
ret.accessed = utils::time::windows_time_t_to_unix_time(u_stat.st_atime);
|
||||
ret.changed = utils::time::windows_time_t_to_unix_time(u_stat.st_ctime);
|
||||
ret.created = utils::time::windows_time_t_to_unix_time(u_stat.st_ctime);
|
||||
ret.modified = utils::time::windows_time_t_to_unix_time(u_stat.st_mtime);
|
||||
ret.written = utils::time::windows_time_t_to_unix_time(u_stat.st_mtime);
|
||||
#else // !defined(_WIN32)
|
||||
struct stat64 u_stat{};
|
||||
if (stat64(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get file times",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
ret.accessed = static_cast<std::uint64_t>(u_stat.st_atimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_atimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.created = static_cast<std::uint64_t>(u_stat.st_birthtimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_birthtimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.changed = static_cast<std::uint64_t>(u_stat.st_ctimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_ctimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.modified = static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.written = static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
#else // !defined(__APPLE__)
|
||||
ret.accessed = static_cast<std::uint64_t>(u_stat.st_atim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_atim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.changed = static_cast<std::uint64_t>(u_stat.st_ctim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_ctim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.created = static_cast<std::uint64_t>(u_stat.st_ctim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_ctim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.modified = static_cast<std::uint64_t>(u_stat.st_mtim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_mtim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.written = static_cast<std::uint64_t>(u_stat.st_mtim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_mtim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
#endif // defined(__APPLE__)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto get_times(std::wstring_view path) -> std::optional<file_times> {
|
||||
return get_times(utils::string::to_utf8(path));
|
||||
}
|
||||
|
||||
auto get_total_drive_space(std::string_view path)
|
||||
-> std::optional<std::uint64_t> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
#if defined(_WIN32)
|
||||
ULARGE_INTEGER li{};
|
||||
if (not ::GetDiskFreeSpaceEx(std::string{path}.c_str(), nullptr, &li,
|
||||
nullptr)) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get total disk space",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
return li.QuadPart;
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
#if defined(__linux__)
|
||||
struct statfs64 u_stat{};
|
||||
if (statfs64(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get total disk space",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
return u_stat.f_blocks * static_cast<std::uint64_t>(u_stat.f_bsize);
|
||||
#endif // defined(__linux__)
|
||||
|
||||
#if defined(__APPLE__)
|
||||
struct statvfs u_stat{};
|
||||
if (statvfs(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get total disk space",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
return u_stat.f_blocks * static_cast<std::uint64_t>(u_stat.f_frsize);
|
||||
#endif // defined(__APPLE__)
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto get_total_drive_space(std::wstring_view path)
|
||||
-> std::optional<std::uint64_t> {
|
||||
return get_total_drive_space(utils::string::to_utf8(path));
|
||||
}
|
||||
|
||||
auto i_fs_item::get_time(time_type type) const -> std::optional<std::uint64_t> {
|
||||
return utils::file::get_time(get_path(), type);
|
||||
}
|
||||
|
||||
auto i_file::read_all(data_buffer &data, std::uint64_t offset,
|
||||
std::size_t *total_read) -> bool {
|
||||
data_buffer buffer;
|
||||
buffer.resize(get_read_buffer_size());
|
||||
|
||||
std::size_t current_read{};
|
||||
while (read(reinterpret_cast<unsigned char *>(buffer.data()),
|
||||
buffer.size() * sizeof(data_buffer::value_type), offset,
|
||||
¤t_read)) {
|
||||
if (total_read != nullptr) {
|
||||
*total_read += current_read;
|
||||
}
|
||||
|
||||
if (current_read != 0U) {
|
||||
offset += current_read;
|
||||
|
||||
data.insert(
|
||||
data.end(), buffer.begin(),
|
||||
std::next(buffer.begin(),
|
||||
static_cast<std::int64_t>(
|
||||
current_read / sizeof(data_buffer::value_type))));
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_JSON)
|
||||
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
auto read_json_file(std::string_view path, nlohmann::json &data,
|
||||
std::optional<std::string_view> password) -> bool {
|
||||
#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
auto read_json_file(std::string_view path, nlohmann::json &data) -> bool {
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto abs_path = utils::path::absolute(path);
|
||||
auto file = file::open_file(abs_path);
|
||||
if (not *file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
data_buffer buffer{};
|
||||
if (not file->read_all(buffer, 0U)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
if (password.has_value()) {
|
||||
data_buffer decrypted_data{};
|
||||
if (not utils::encryption::decrypt_data(*password, buffer,
|
||||
decrypted_data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer = decrypted_data;
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
|
||||
std::string json_str(buffer.begin(), buffer.end());
|
||||
if (not json_str.empty()) {
|
||||
data = nlohmann::json::parse(json_str);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
return false;
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
auto write_json_file(std::string_view path, const nlohmann::json &data,
|
||||
std::optional<std::string_view> password) -> bool {
|
||||
#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
auto write_json_file(std::string_view path, const nlohmann::json &data)
|
||||
-> bool {
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto file = file::open_or_create_file(path);
|
||||
if (not file->truncate()) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to truncate file",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
if (password.has_value()) {
|
||||
const auto str_data = data.dump(2);
|
||||
|
||||
data_buffer encrypted_data{};
|
||||
utils::encryption::encrypt_data(
|
||||
*password, reinterpret_cast<const unsigned char *>(str_data.c_str()),
|
||||
str_data.size(), encrypted_data);
|
||||
return file->write(encrypted_data, 0U);
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
|
||||
auto json_str = data.dump(2);
|
||||
return file->write(
|
||||
reinterpret_cast<const unsigned char *>(json_str.c_str()),
|
||||
json_str.size(), 0U);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
auto read_json_file(std::wstring_view path, nlohmann::json &data,
|
||||
std::optional<std::wstring_view> password) -> bool {
|
||||
if (password.has_value()) {
|
||||
auto password_a = utils::string::to_utf8(*password);
|
||||
return read_json_file(utils::string::to_utf8(path), data, password_a);
|
||||
}
|
||||
|
||||
return read_json_file(utils::string::to_utf8(path), data, std::nullopt);
|
||||
}
|
||||
|
||||
auto write_json_file(std::wstring_view path, const nlohmann::json &data,
|
||||
std::optional<std::wstring_view> password) -> bool {
|
||||
if (password.has_value()) {
|
||||
auto password_a = utils::string::to_utf8(*password);
|
||||
return write_json_file(utils::string::to_utf8(path), data, password_a);
|
||||
}
|
||||
|
||||
return write_json_file(utils::string::to_utf8(path), data, std::nullopt);
|
||||
}
|
||||
#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
auto read_json_file(std::wstring_view path, nlohmann::json &data) -> bool {
|
||||
return read_json_file(utils::string::to_utf8(path), data);
|
||||
}
|
||||
|
||||
auto write_json_file(std::wstring_view path, const nlohmann::json &data)
|
||||
-> bool {
|
||||
return write_json_file(utils::string::to_utf8(path), data);
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
#endif // defined(PROJECT_ENABLE_JSON)
|
||||
|
||||
#if defined(PROJECT_ENABLE_LIBDSM)
|
||||
static constexpr auto validate_smb_path = [](std::string_view path) -> bool {
|
||||
return (not utils::string::begins_with(path, "///") &&
|
||||
utils::string::begins_with(path, "//") &&
|
||||
// not utils::string::contains(path, " ") &&
|
||||
std::count(path.begin(), path.end(), '/') >= 3U);
|
||||
};
|
||||
|
||||
auto smb_create_smb_path(std::string_view smb_path, std::string_view rel_path)
|
||||
-> std::string {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not validate_smb_path(smb_path)) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"invalid smb path",
|
||||
smb_path,
|
||||
});
|
||||
}
|
||||
|
||||
std::string path{rel_path};
|
||||
utils::path::format_path(path, "/", "\\");
|
||||
utils::string::left_trim(path, '/');
|
||||
|
||||
auto old_parts =
|
||||
fifthgrid::utils::string::split(smb_path.substr(2U), '/', false);
|
||||
|
||||
auto new_parts = fifthgrid::utils::string::split(path, '/', false);
|
||||
old_parts.insert(old_parts.end(), new_parts.begin(), new_parts.end());
|
||||
|
||||
path = utils::string::join(old_parts, '/');
|
||||
path = "//" + utils::path::format_path(path, "/", "\\");
|
||||
|
||||
if (not validate_smb_path(path)) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"invalid smb path",
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
auto smb_create_and_validate_relative_path(std::string_view smb_path,
|
||||
std::string_view path)
|
||||
-> std::string {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not validate_smb_path(smb_path)) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"invalid smb path",
|
||||
smb_path,
|
||||
});
|
||||
}
|
||||
|
||||
std::string dir_path;
|
||||
if (utils::string::begins_with(path, "//")) {
|
||||
if (not utils::file::smb_parent_is_same(smb_path, path)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to validate path",
|
||||
"parent paths are not the same",
|
||||
smb_path,
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
return utils::file::smb_create_relative_path(path);
|
||||
}
|
||||
|
||||
return utils::file::smb_create_relative_path(std::string{smb_path} + '/' +
|
||||
std::string{path});
|
||||
}
|
||||
|
||||
auto smb_create_relative_path(std::string_view smb_path) -> std::string {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not validate_smb_path(smb_path)) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"invalid smb path",
|
||||
smb_path,
|
||||
});
|
||||
}
|
||||
|
||||
std::string path{smb_path};
|
||||
utils::path::format_path(path, "\\", "/");
|
||||
utils::string::left_trim(path, '\\');
|
||||
|
||||
auto parts = fifthgrid::utils::string::split(path, '\\', false);
|
||||
parts.erase(parts.begin(), std::next(parts.begin(), 2U));
|
||||
return "\\" + utils::string::join(parts, '\\');
|
||||
}
|
||||
|
||||
auto smb_create_search_path(std::string_view smb_path) -> std::string {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not validate_smb_path(smb_path)) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"invalid smb path",
|
||||
smb_path,
|
||||
});
|
||||
}
|
||||
|
||||
std::string path{smb_path};
|
||||
utils::string::left_trim(path, '/');
|
||||
|
||||
auto parts = fifthgrid::utils::string::split(path, '/', false);
|
||||
parts.erase(parts.begin(), std::next(parts.begin(), 2U));
|
||||
|
||||
auto search_path = fifthgrid::utils::string::join(parts, '\\');
|
||||
return search_path.empty() ? "\\*" : "\\" + search_path + "\\*";
|
||||
}
|
||||
|
||||
auto smb_get_parent_path(std::string_view smb_path) -> std::string {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not validate_smb_path(smb_path)) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"invalid smb path",
|
||||
smb_path,
|
||||
});
|
||||
}
|
||||
|
||||
auto parts = fifthgrid::utils::string::split(smb_path.substr(2U), '/', false);
|
||||
if (parts.size() > 2U) {
|
||||
parts.erase(std::prev(parts.end()), parts.end());
|
||||
}
|
||||
|
||||
auto parent_smb_path = "//" + utils::string::join(parts, '/');
|
||||
if (not validate_smb_path(parent_smb_path)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"invalid parent smb path",
|
||||
parent_smb_path,
|
||||
});
|
||||
}
|
||||
|
||||
return parent_smb_path;
|
||||
}
|
||||
|
||||
auto smb_get_root_path(std::string_view smb_path) -> std::string {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not validate_smb_path(smb_path)) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"invalid smb path",
|
||||
smb_path,
|
||||
});
|
||||
}
|
||||
|
||||
auto parts = fifthgrid::utils::string::split(smb_path.substr(2U), '/', false);
|
||||
if (parts.size() > 2U) {
|
||||
parts.erase(std::next(parts.begin(), 2U), parts.end());
|
||||
}
|
||||
|
||||
return "//" + utils::string::join(parts, '/');
|
||||
}
|
||||
|
||||
auto smb_get_unc_path(std::string_view smb_path) -> std::string {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not validate_smb_path(smb_path)) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"invalid smb path",
|
||||
smb_path,
|
||||
});
|
||||
}
|
||||
|
||||
std::string unc_path{smb_path};
|
||||
utils::path::format_path(unc_path, "\\", "/");
|
||||
return '\\' + unc_path;
|
||||
}
|
||||
|
||||
auto smb_get_uri_path(std::string_view smb_path) -> std::string {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not validate_smb_path(smb_path)) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"invalid smb path",
|
||||
smb_path,
|
||||
});
|
||||
}
|
||||
|
||||
return "smb:" + std::string{smb_path};
|
||||
}
|
||||
|
||||
auto smb_get_uri_path(std::string_view smb_path, std::string_view user,
|
||||
std::string_view password) -> std::string {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not validate_smb_path(smb_path)) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"invalid smb path",
|
||||
smb_path,
|
||||
});
|
||||
}
|
||||
|
||||
return "smb://" + std::string{user} + ':' + std::string{password} + '@' +
|
||||
std::string{smb_path.substr(2U)};
|
||||
}
|
||||
|
||||
auto smb_parent_is_same(std::string_view smb_path1, std::string_view smb_path2)
|
||||
-> bool {
|
||||
if (not(validate_smb_path(smb_path1) && validate_smb_path(smb_path2))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto parts1 = utils::string::split(smb_path1.substr(2U), "/", false);
|
||||
auto parts2 = utils::string::split(smb_path2.substr(2U), "/", false);
|
||||
if (parts1.size() < 2U || parts2.size() < 2U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parts2.at(1U).empty() || parts1.at(1U).empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::equal(parts1.begin(), std::next(parts1.begin(), 2U),
|
||||
parts2.begin());
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_LIBDSM)
|
||||
} // namespace fifthgrid::utils::file
|
||||
597
support/src/utils/file_directory.cpp
Normal file
597
support/src/utils/file_directory.cpp
Normal file
@@ -0,0 +1,597 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "utils/file_directory.hpp"
|
||||
|
||||
#include "utils/com_init_wrapper.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/unix.hpp"
|
||||
#include "utils/windows.hpp"
|
||||
|
||||
namespace {
|
||||
auto traverse_directory(
|
||||
std::string_view path,
|
||||
std::function<bool(fifthgrid::utils::file::directory)> directory_action,
|
||||
std::function<bool(fifthgrid::utils::file::file)> file_action,
|
||||
fifthgrid::stop_type *stop_requested) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
auto res{true};
|
||||
|
||||
const auto is_stop_requested = [&stop_requested]() -> bool {
|
||||
return (stop_requested != nullptr && *stop_requested);
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
WIN32_FIND_DATAA fd{};
|
||||
auto search = fifthgrid::utils::path::combine(path, {"*.*"});
|
||||
auto find = ::FindFirstFileA(search.c_str(), &fd);
|
||||
if (find == INVALID_HANDLE_VALUE) {
|
||||
throw fifthgrid::utils::error::create_exception(
|
||||
function_name,
|
||||
{
|
||||
"failed to open directory",
|
||||
std::to_string(fifthgrid::utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
do {
|
||||
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
if ((std::string_view(fd.cFileName) != ".") &&
|
||||
(std::string_view(fd.cFileName) != "..")) {
|
||||
res = directory_action(fifthgrid::utils::file::directory{
|
||||
fifthgrid::utils::path::combine(path, {fd.cFileName}),
|
||||
stop_requested,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res = file_action(fifthgrid::utils::file::file(
|
||||
fifthgrid::utils::path::combine(path, {fd.cFileName})));
|
||||
}
|
||||
} while (res && (::FindNextFileA(find, &fd) != 0) && !is_stop_requested());
|
||||
|
||||
::FindClose(find);
|
||||
#else // !defined(_WIN32)
|
||||
auto *root = opendir(std::string{path}.c_str());
|
||||
if (root == nullptr) {
|
||||
throw fifthgrid::utils::error::create_exception(
|
||||
function_name,
|
||||
{
|
||||
"failed to open directory",
|
||||
std::to_string(fifthgrid::utils::get_last_error_code()),
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
struct dirent *entry{nullptr};
|
||||
while (res && (entry = ::readdir(root)) && !is_stop_requested()) {
|
||||
if (entry->d_type == DT_DIR) {
|
||||
if ((std::string_view(entry->d_name) == ".") ||
|
||||
(std::string_view(entry->d_name) == "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
res = directory_action(fifthgrid::utils::file::directory(
|
||||
fifthgrid::utils::path::combine(path, {entry->d_name})));
|
||||
} else {
|
||||
res = file_action(fifthgrid::utils::file::file(
|
||||
fifthgrid::utils::path::combine(path, {entry->d_name})));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(root);
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
return res;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace fifthgrid::utils::file {
|
||||
auto directory::copy_to(std::string_view new_path, bool overwrite) const
|
||||
-> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not exists()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to copy directory",
|
||||
"source does not exist",
|
||||
path_,
|
||||
std::string{new_path},
|
||||
});
|
||||
}
|
||||
|
||||
auto src_root = utils::path::finalize(path_);
|
||||
auto dst_root = utils::path::finalize(new_path);
|
||||
|
||||
if (directory{dst_root}.exists()) {
|
||||
auto src_base = utils::path::strip_to_file_name(src_root);
|
||||
dst_root = utils::path::combine(dst_root, {src_base});
|
||||
} else {
|
||||
auto dst_parent = utils::path::get_parent_path(dst_root);
|
||||
if (not dst_parent.empty() && not directory{dst_parent}.exists()) {
|
||||
auto parent_parent = utils::path::get_parent_path(dst_parent);
|
||||
auto last_piece = utils::path::strip_to_file_name(dst_parent);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{parent_parent}.create_directory(last_piece);
|
||||
}
|
||||
if (not directory{dst_root}.exists()) {
|
||||
auto root_parent = utils::path::get_parent_path(dst_root);
|
||||
auto root_name = utils::path::strip_to_file_name(dst_root);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{root_parent}.create_directory(root_name);
|
||||
}
|
||||
}
|
||||
|
||||
auto success = traverse_directory(
|
||||
src_root,
|
||||
[this, &dst_root, &src_root](auto &&dir_item) -> bool {
|
||||
auto child_src = dir_item.get_path();
|
||||
auto rel_path = utils::path::get_relative_path(child_src, src_root);
|
||||
auto child_dst = utils::path::combine(dst_root, {rel_path});
|
||||
|
||||
auto child_parent = utils::path::get_parent_path(child_dst);
|
||||
auto child_name = utils::path::strip_to_file_name(child_dst);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{child_parent}.create_directory(child_name);
|
||||
return not is_stop_requested();
|
||||
},
|
||||
[this, &dst_root, overwrite, &src_root](auto &&file_item) -> bool {
|
||||
auto child_src = file_item.get_path();
|
||||
auto rel_path = utils::path::get_relative_path(child_src, src_root);
|
||||
auto child_dst = utils::path::combine(dst_root, {rel_path});
|
||||
|
||||
if (not file{child_src}.copy_to(child_dst, overwrite)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return not is_stop_requested();
|
||||
},
|
||||
stop_requested_);
|
||||
|
||||
return success && not is_stop_requested();
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::handle_exception(function_name, ex);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto directory::count(bool recursive) const -> std::uint64_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
std::uint64_t ret{0U};
|
||||
|
||||
traverse_directory(
|
||||
path_,
|
||||
[&ret, &recursive](auto &&dir_item) -> bool {
|
||||
if (recursive) {
|
||||
ret += dir_item.count(true);
|
||||
}
|
||||
|
||||
++ret;
|
||||
return true;
|
||||
},
|
||||
[&ret](auto && /* file_item */) -> bool {
|
||||
++ret;
|
||||
return true;
|
||||
},
|
||||
stop_requested_);
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return 0U;
|
||||
}
|
||||
|
||||
auto directory::create_directory(std::string_view path) const
|
||||
-> fs_directory_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto abs_path = utils::path::combine(path_, {path});
|
||||
if (directory{abs_path, stop_requested_}.exists()) {
|
||||
return std::make_unique<directory>(abs_path);
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
|
||||
|
||||
auto res = ::SHCreateDirectory(nullptr,
|
||||
utils::string::from_utf8(abs_path).c_str());
|
||||
if (res != ERROR_SUCCESS) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to create directory",
|
||||
std::to_string(res),
|
||||
abs_path,
|
||||
});
|
||||
}
|
||||
#else // !defined(_WIN32)
|
||||
auto ret{true};
|
||||
auto paths =
|
||||
utils::string::split(abs_path, utils::path::directory_seperator, false);
|
||||
|
||||
std::string current_path;
|
||||
for (std::size_t idx = 0U;
|
||||
!is_stop_requested() && ret && (idx < paths.size()); ++idx) {
|
||||
if (paths.at(idx).empty()) {
|
||||
current_path = utils::path::directory_seperator;
|
||||
continue;
|
||||
}
|
||||
|
||||
current_path = utils::path::combine(current_path, {paths.at(idx)});
|
||||
auto status = mkdir(current_path.c_str(), S_IRWXU);
|
||||
ret = ((status == 0) || (errno == EEXIST));
|
||||
}
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
return std::make_unique<directory>(abs_path);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto directory::exists() const -> bool {
|
||||
#if defined(_WIN32)
|
||||
return ::PathIsDirectoryA(path_.c_str()) != 0;
|
||||
#else // !defined(_WIN32)
|
||||
struct stat64 u_stat{};
|
||||
return (stat64(path_.c_str(), &u_stat) == 0 && S_ISDIR(u_stat.st_mode));
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto directory::get_directory(std::string_view path) const -> fs_directory_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto dir_path = utils::path::combine(path_, {path});
|
||||
return fs_directory_t{
|
||||
directory{dir_path, stop_requested_}.exists()
|
||||
? new directory(dir_path, stop_requested_)
|
||||
: nullptr,
|
||||
};
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto directory::get_directories() const -> std::vector<fs_directory_t> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
std::vector<fs_directory_t> ret{};
|
||||
|
||||
traverse_directory(
|
||||
path_,
|
||||
[this, &ret](auto &&dir_item) -> bool {
|
||||
ret.emplace_back(fs_directory_t{
|
||||
new directory(dir_item.get_path(), stop_requested_),
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
[](auto && /* file_item */) -> bool { return true; }, stop_requested_);
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto directory::create_file(std::string_view file_name, bool read_only) const
|
||||
-> fs_file_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto file_path = utils::path::combine(path_, {file_name});
|
||||
return file::open_or_create_file(file_path, read_only);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto directory::get_file(std::string_view path) const -> fs_file_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto file_path = utils::path::combine(path_, {path});
|
||||
return fs_file_t{
|
||||
file{file_path}.exists() ? new file(file_path) : nullptr,
|
||||
};
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto directory::get_files() const -> std::vector<fs_file_t> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
std::vector<fs_file_t> ret{};
|
||||
|
||||
traverse_directory(
|
||||
path_, [](auto && /* dir_item */) -> bool { return true; },
|
||||
[&ret](auto &&file_item) -> bool {
|
||||
ret.emplace_back(fs_file_t{
|
||||
new file(file_item.get_path()),
|
||||
});
|
||||
return true;
|
||||
},
|
||||
stop_requested_);
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto directory::get_items() const -> std::vector<fs_item_t> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
std::vector<fs_item_t> ret{};
|
||||
|
||||
traverse_directory(
|
||||
path_,
|
||||
[this, &ret](auto &&dir_item) -> bool {
|
||||
ret.emplace_back(fs_item_t{
|
||||
new directory(dir_item.get_path(), stop_requested_),
|
||||
});
|
||||
return true;
|
||||
},
|
||||
[&ret](auto &&file_item) -> bool {
|
||||
ret.emplace_back(fs_item_t{
|
||||
new file(file_item.get_path()),
|
||||
});
|
||||
return true;
|
||||
},
|
||||
stop_requested_);
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto directory::is_stop_requested() const -> bool {
|
||||
return (stop_requested_ != nullptr) && *stop_requested_;
|
||||
}
|
||||
|
||||
auto directory::is_symlink() const -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
return std::filesystem::is_symlink(path_);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto directory::move_to(std::string_view new_path) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto src_root = utils::path::finalize(path_);
|
||||
auto dst_root = utils::path::finalize(new_path);
|
||||
if (src_root == dst_root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (directory{dst_root}.exists()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move directory",
|
||||
"destination exists",
|
||||
src_root,
|
||||
dst_root,
|
||||
});
|
||||
}
|
||||
|
||||
auto dst_parent = utils::path::get_parent_path(dst_root);
|
||||
if (not dst_parent.empty() && not directory{dst_parent}.exists()) {
|
||||
auto parent_parent = utils::path::get_parent_path(dst_parent);
|
||||
auto last_piece = utils::path::strip_to_file_name(dst_parent);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{parent_parent}.create_directory(last_piece);
|
||||
}
|
||||
|
||||
if (not directory{dst_root}.exists()) {
|
||||
auto root_parent = utils::path::get_parent_path(dst_root);
|
||||
auto root_name = utils::path::strip_to_file_name(dst_root);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{root_parent}.create_directory(root_name);
|
||||
}
|
||||
|
||||
if (not copy_to(dst_root, true)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move directory",
|
||||
"copy failed",
|
||||
src_root,
|
||||
dst_root,
|
||||
});
|
||||
}
|
||||
|
||||
if (is_stop_requested()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not remove_recursively()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move directory",
|
||||
"remove source failed",
|
||||
src_root,
|
||||
});
|
||||
}
|
||||
|
||||
path_ = dst_root;
|
||||
return true;
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::handle_exception(function_name, ex);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto directory::remove() -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
return utils::retry_action([this]() -> bool {
|
||||
try {
|
||||
#if defined(_WIN32)
|
||||
auto ret = not exists() || (::RemoveDirectoryA(path_.c_str()) != 0);
|
||||
#else // !defined(_WIN32)
|
||||
auto ret = not exists() || (rmdir(path_.c_str()) == 0);
|
||||
#endif // defined(_WIN32)
|
||||
if (not ret) {
|
||||
utils::error::handle_error(function_name,
|
||||
utils::error::create_error_message({
|
||||
"failed to remove directory",
|
||||
path_,
|
||||
}));
|
||||
}
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
auto directory::remove_recursively() -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (not traverse_directory(
|
||||
path_,
|
||||
[](auto &&dir_item) -> bool {
|
||||
return dir_item.remove_recursively();
|
||||
},
|
||||
[](auto &&file_item) -> bool { return file_item.remove(); },
|
||||
stop_requested_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return remove();
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto directory::size(bool recursive) const -> std::uint64_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
std::uint64_t ret{0U};
|
||||
|
||||
traverse_directory(
|
||||
path_,
|
||||
[&ret, &recursive](auto &&dir_item) -> bool {
|
||||
if (recursive) {
|
||||
ret += dir_item.size(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[&ret](auto &&file_item) -> bool {
|
||||
auto cur_size = file_item.size();
|
||||
if (cur_size.has_value()) {
|
||||
ret += cur_size.value();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
stop_requested_);
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return 0U;
|
||||
}
|
||||
} // namespace fifthgrid::utils::file
|
||||
189
support/src/utils/file_enc_file.cpp
Normal file
189
support/src/utils/file_enc_file.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
|
||||
#include "utils/file_enc_file.hpp"
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
#include "utils/encryption.hpp"
|
||||
|
||||
namespace fifthgrid::utils::file {
|
||||
auto enc_file::attach_file(fs_file_t file) -> fs_file_t {
|
||||
return fs_file_t{
|
||||
new enc_file(std::move(file)),
|
||||
};
|
||||
}
|
||||
|
||||
enc_file::enc_file(fs_file_t file) : file_(std::move(file)) {}
|
||||
|
||||
void enc_file::close() { file_->close(); }
|
||||
|
||||
auto enc_file::copy_to(std::string_view new_path, bool overwrite) const
|
||||
-> bool {
|
||||
return file_->copy_to(new_path, overwrite);
|
||||
}
|
||||
|
||||
void enc_file::flush() const { file_->flush(); }
|
||||
|
||||
auto enc_file::move_to(std::string_view path) -> bool {
|
||||
return file_->move_to(path);
|
||||
}
|
||||
|
||||
auto enc_file::read(unsigned char *data, std::size_t to_read,
|
||||
std::uint64_t offset, std::size_t *total_read) -> bool {
|
||||
if (total_read != nullptr) {
|
||||
*total_read = 0U;
|
||||
}
|
||||
|
||||
auto file_size{size()};
|
||||
if (not file_size.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to_read = utils::calculate_read_size(file_size.value(), to_read, offset);
|
||||
if (to_read == 0U) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t bytes_read{};
|
||||
auto ret{
|
||||
utils::encryption::read_encrypted_range(
|
||||
{.begin = offset, .end = offset + to_read - 1U},
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(
|
||||
encryption_token_),
|
||||
[&](auto &&ct_buffer, auto &&start_offset,
|
||||
auto &&end_offset) -> bool {
|
||||
ct_buffer.resize(end_offset - start_offset + 1U);
|
||||
return file_->read(ct_buffer, start_offset);
|
||||
},
|
||||
file_size.value(), data, to_read, bytes_read),
|
||||
};
|
||||
if (ret && total_read != nullptr) {
|
||||
*total_read = bytes_read;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto enc_file::remove() -> bool { return file_->remove(); }
|
||||
|
||||
auto enc_file::truncate(std::size_t size) -> bool {
|
||||
if (size == 0U) {
|
||||
return file_->truncate(size);
|
||||
}
|
||||
|
||||
auto file_size{this->size()};
|
||||
if (not file_size.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (size == file_size.value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto chunks{
|
||||
size / utils::encryption::encrypting_reader::get_data_chunk_size(),
|
||||
};
|
||||
auto real_size{
|
||||
(chunks * utils::encryption::encrypting_reader::get_data_chunk_size()) +
|
||||
(chunks * utils::encryption::encrypting_reader::get_header_size()),
|
||||
};
|
||||
auto remain{
|
||||
size % utils::encryption::encrypting_reader::get_data_chunk_size(),
|
||||
};
|
||||
if (remain > 0U) {
|
||||
real_size +=
|
||||
(remain + utils::encryption::encrypting_reader::get_header_size());
|
||||
}
|
||||
|
||||
if (size < file_size.value()) {
|
||||
if (remain == 0U) {
|
||||
return file_->truncate(real_size);
|
||||
}
|
||||
|
||||
auto begin_chunk{
|
||||
size / utils::encryption::encrypting_reader::get_data_chunk_size(),
|
||||
};
|
||||
|
||||
auto offset{
|
||||
begin_chunk *
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size(),
|
||||
};
|
||||
|
||||
std::size_t total_read{};
|
||||
data_buffer data(
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size());
|
||||
if (not i_file::read(data, offset, &total_read)) {
|
||||
return false;
|
||||
}
|
||||
data.resize(remain);
|
||||
|
||||
if (not file_->truncate(real_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return i_file::write(data, offset);
|
||||
}
|
||||
|
||||
/* auto begin_chunk{
|
||||
file_size.value() /
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size(),
|
||||
};
|
||||
auto end_chunk{
|
||||
utils::divide_with_ceiling(
|
||||
file_size.value(),
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size()),
|
||||
}; */
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto enc_file::write(const unsigned char * /* data */, std::size_t to_write,
|
||||
std::size_t offset, std::size_t * /* total_written */)
|
||||
-> bool {
|
||||
auto file_size{size()};
|
||||
if (not file_size.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((offset + to_write) > file_size.value()) {
|
||||
if (not truncate((offset + to_write) - file_size.value())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto enc_file::size() const -> std::optional<std::uint64_t> {
|
||||
auto file_size = file_->size();
|
||||
if (not file_size.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return utils::encryption::encrypting_reader::calculate_decrypted_size(
|
||||
file_size.value(), false);
|
||||
}
|
||||
} // namespace fifthgrid::utils::file
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
595
support/src/utils/file_file.cpp
Normal file
595
support/src/utils/file_file.cpp
Normal file
@@ -0,0 +1,595 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "utils/file_file.hpp"
|
||||
|
||||
#include "utils/collection.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/path.hpp"
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] auto get_file_size(std::string_view path,
|
||||
std::uint64_t &file_size) -> bool {
|
||||
auto abs_path = fifthgrid::utils::path::absolute(path);
|
||||
file_size = 0U;
|
||||
|
||||
#if defined(_WIN32)
|
||||
struct _stat64 u_stat{};
|
||||
auto res = _stat64(std::string{path}.c_str(), &u_stat);
|
||||
if (res != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_size = static_cast<std::uint64_t>(u_stat.st_size);
|
||||
return true;
|
||||
#else // !defined(_WIN32)
|
||||
std::error_code ec{};
|
||||
file_size = std::filesystem::file_size(abs_path, ec);
|
||||
return (ec.value() == 0);
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
|
||||
[[nodiscard]] auto is_file(std::string_view path) -> bool {
|
||||
auto abs_path = fifthgrid::utils::path::absolute(path);
|
||||
|
||||
#if defined(_WIN32)
|
||||
return ((::PathFileExistsA(abs_path.c_str()) != 0) &&
|
||||
(::PathIsDirectoryA(abs_path.c_str()) == 0));
|
||||
#else // !defined(_WIN32)
|
||||
struct stat64 u_stat{};
|
||||
return (stat64(abs_path.c_str(), &u_stat) == 0 &&
|
||||
not S_ISDIR(u_stat.st_mode));
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace fifthgrid::utils::file {
|
||||
// auto file::attach_file(native_handle handle,
|
||||
// bool read_only) -> fs_file_t {
|
||||
// FIFTHGRID_USES_FUNCTION_NAME();
|
||||
//
|
||||
// try {
|
||||
// std::string path;
|
||||
//
|
||||
// #if defined(_WIN32)
|
||||
// path.resize(fifthgrid::max_path_length + 1U);
|
||||
// ::GetFinalPathNameByHandleA(handle, path.data(),
|
||||
// static_cast<DWORD>(path.size()),
|
||||
// FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
|
||||
// #else // !defined(_WIN32)
|
||||
// path.resize(fifthgrid::max_path_length + 1U);
|
||||
//
|
||||
// #if defined(__APPLE__)
|
||||
// fcntl(handle, F_GETPATH, source_path.data());
|
||||
// #else // !defined(__APPLE__)
|
||||
// readlink(("/proc/self/fd/" + std::to_string(handle)).c_str(),
|
||||
// path.data(),
|
||||
// path.size());
|
||||
// #endif // defined(__APPLE__)
|
||||
// #endif // defined(_WIN32)
|
||||
//
|
||||
// path = path.c_str();
|
||||
//
|
||||
// #if defined(_WIN32)
|
||||
// auto *ptr = _fdopen(
|
||||
// static_cast<int>(_open_osfhandle(reinterpret_cast<intptr_t>(handle),
|
||||
// read_only ? _O_RDONLY : _O_RDWR)),
|
||||
// read_only ? "rb" : "rb+");
|
||||
// #else // !defined(_WIN32)
|
||||
// auto *ptr = fdopen(handle, read_only ? "rb" : "rb+");
|
||||
// #endif // defined(_WIN32)
|
||||
//
|
||||
// return fs_file_t(new file{
|
||||
// file_t{ptr},
|
||||
// utils::path::absolute(path),
|
||||
// read_only,
|
||||
// });
|
||||
// } catch (const std::exception &e) {
|
||||
// utils::error::handle_exception(function_name, e);
|
||||
// } catch (...) {
|
||||
// utils::error::handle_exception(function_name);
|
||||
// }
|
||||
//
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
void file::open() {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (not is_file(path_)) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"file not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
file_ = file_t{
|
||||
_fsopen(path_.c_str(), read_only_ ? "rb" : "rb+", _SH_DENYNO),
|
||||
file_deleter(),
|
||||
};
|
||||
#else // !defined(_WIN32)
|
||||
file_ = file_t{
|
||||
fopen(path_.c_str(), read_only_ ? "rb" : "rb+"),
|
||||
file_deleter(),
|
||||
};
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
|
||||
auto file::open_file(std::string_view path, bool read_only) -> fs_file_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
auto *ptr = new file{
|
||||
nullptr,
|
||||
utils::path::absolute(path),
|
||||
read_only,
|
||||
};
|
||||
auto new_file = fs_file_t(ptr);
|
||||
|
||||
try {
|
||||
ptr->open();
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return new_file;
|
||||
}
|
||||
|
||||
auto file::open_or_create_file(std::string_view path, bool read_only)
|
||||
-> fs_file_t {
|
||||
auto abs_path = utils::path::absolute(path);
|
||||
if (not is_file(abs_path)) {
|
||||
#if defined(_WIN32)
|
||||
int old_mode{};
|
||||
_umask_s(077, &old_mode);
|
||||
#else // !defined(_WIN32)
|
||||
auto old_mode = umask(077);
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
#if defined(_WIN32)
|
||||
auto *ptr = _fsopen(abs_path.c_str(), "ab+", _SH_DENYNO);
|
||||
#else // !defined(_WIN32)
|
||||
auto *ptr = fopen(abs_path.c_str(), "ab+");
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
if (ptr != nullptr) {
|
||||
fclose(ptr);
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
_umask_s(old_mode, nullptr);
|
||||
#else // !defined(_WIN32)
|
||||
umask(old_mode);
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
|
||||
return open_file(abs_path, read_only);
|
||||
}
|
||||
|
||||
void file::close() { file_.reset(); }
|
||||
|
||||
auto file::copy_to(std::string_view new_path, bool overwrite) const -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto to_path = utils::path::absolute(new_path);
|
||||
if (directory(to_path).exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
return ::CopyFileA(path_.c_str(), to_path.c_str(),
|
||||
overwrite ? TRUE : FALSE) != 0;
|
||||
#else // !defined(_WIN32)
|
||||
return std::filesystem::copy_file(
|
||||
path_, to_path,
|
||||
overwrite ? std::filesystem::copy_options::overwrite_existing
|
||||
: std::filesystem::copy_options::skip_existing);
|
||||
#endif // defined(_WIN32)
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto file::exists() const -> bool { return is_file(path_); }
|
||||
|
||||
void file::flush() const {
|
||||
if (file_) {
|
||||
fflush(file_.get());
|
||||
}
|
||||
}
|
||||
|
||||
auto file::get_handle() const -> native_handle {
|
||||
if (file_) {
|
||||
#if defined(_WIN32)
|
||||
return reinterpret_cast<native_handle>(
|
||||
_get_osfhandle(_fileno(file_.get())));
|
||||
#else // !defined(_WIN32)
|
||||
return fileno(file_.get());
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
auto file::is_symlink() const -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
return std::filesystem::is_symlink(path_);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto file::move_to(std::string_view path) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
auto abs_path = utils::path::absolute(path);
|
||||
|
||||
auto reopen{false};
|
||||
if (file_) {
|
||||
reopen = true;
|
||||
close();
|
||||
}
|
||||
|
||||
auto success{false};
|
||||
#if defined(_WIN32)
|
||||
success = ::MoveFileExA(path_.c_str(), abs_path.c_str(),
|
||||
MOVEFILE_REPLACE_EXISTING) != 0;
|
||||
#else // !defined(_WIN32)
|
||||
std::error_code ec{};
|
||||
std::filesystem::rename(path_, abs_path, ec);
|
||||
success = ec.value() == 0;
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
if (success) {
|
||||
path_ = abs_path;
|
||||
}
|
||||
|
||||
if (reopen) {
|
||||
try {
|
||||
open();
|
||||
return success;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto file::read(unsigned char *data, std::size_t to_read, std::uint64_t offset,
|
||||
std::size_t *total_read) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (total_read != nullptr) {
|
||||
(*total_read) = 0U;
|
||||
}
|
||||
|
||||
try {
|
||||
if (not file_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"file is not open for reading",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
if (fseeko(file_.get(), static_cast<std::int64_t>(offset), SEEK_SET) ==
|
||||
-1) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to seek before read",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t bytes_read{0U};
|
||||
while (bytes_read != to_read) {
|
||||
auto res =
|
||||
fread(&data[bytes_read], 1U, to_read - bytes_read, file_.get());
|
||||
if (not feof(file_.get()) && ferror(file_.get())) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to read file bytes",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
if (res == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
bytes_read += static_cast<std::size_t>(res);
|
||||
}
|
||||
|
||||
if (total_read != nullptr) {
|
||||
(*total_read) = bytes_read;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_LIBSODIUM)
|
||||
auto file::sha256() -> std::optional<std::string> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
auto should_close{false};
|
||||
auto read_only{read_only_};
|
||||
std::optional<std::string> ret;
|
||||
|
||||
try {
|
||||
if (file_ == nullptr) {
|
||||
should_close = true;
|
||||
read_only_ = true;
|
||||
this->open();
|
||||
}
|
||||
|
||||
crypto_hash_sha256_state state{};
|
||||
auto res = crypto_hash_sha256_init(&state);
|
||||
if (res != 0) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to initialize sha256",
|
||||
std::to_string(res),
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
data_buffer buffer(get_read_buffer_size());
|
||||
std::uint64_t read_offset{0U};
|
||||
std::size_t bytes_read{0U};
|
||||
while (i_file::read(buffer, read_offset, &bytes_read)) {
|
||||
if (not bytes_read) {
|
||||
break;
|
||||
}
|
||||
|
||||
read_offset += bytes_read;
|
||||
res = crypto_hash_sha256_update(
|
||||
&state, reinterpret_cast<const unsigned char *>(buffer.data()),
|
||||
bytes_read);
|
||||
if (res != 0) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to update sha256",
|
||||
std::to_string(res),
|
||||
path_,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::array<unsigned char, crypto_hash_sha256_BYTES> out{};
|
||||
res = crypto_hash_sha256_final(&state, out.data());
|
||||
if (res != 0) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to finalize sha256",
|
||||
std::to_string(res),
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
ret = utils::collection::to_hex_string(out);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
if (should_close) {
|
||||
read_only_ = read_only;
|
||||
close();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
|
||||
|
||||
auto file::remove() -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
close();
|
||||
|
||||
return utils::retry_action([this]() -> bool {
|
||||
try {
|
||||
#if defined(_WIN32)
|
||||
auto ret = not exists() || (::DeleteFileA(path_.c_str()) != 0);
|
||||
#else // !defined(_WIN32)
|
||||
std::error_code ec{};
|
||||
auto ret = not exists() || std::filesystem::remove(path_, ec);
|
||||
#endif // defined(_WIN32)
|
||||
if (not ret) {
|
||||
utils::error::handle_error(function_name,
|
||||
utils::error::create_error_message({
|
||||
"failed to remove file",
|
||||
path_,
|
||||
}));
|
||||
}
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
auto file::truncate(std::size_t size) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
auto reopen{false};
|
||||
if (file_) {
|
||||
reopen = true;
|
||||
close();
|
||||
}
|
||||
|
||||
std::error_code ec{};
|
||||
std::filesystem::resize_file(path_, size, ec);
|
||||
|
||||
auto success{ec.value() == 0};
|
||||
|
||||
if (reopen) {
|
||||
try {
|
||||
open();
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
success = false;
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
auto file::write(const unsigned char *data, std::size_t to_write,
|
||||
std::size_t offset, std::size_t *total_written) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
if (total_written != nullptr) {
|
||||
(*total_written) = 0U;
|
||||
}
|
||||
|
||||
try {
|
||||
if (not file_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"file is not open for writing",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
auto res = fseeko(file_.get(), static_cast<std::int64_t>(offset), SEEK_SET);
|
||||
if (res == -1) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to seek before write",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t bytes_written{0U};
|
||||
while (bytes_written != to_write) {
|
||||
auto written =
|
||||
fwrite(reinterpret_cast<const char *>(&data[bytes_written]), 1U,
|
||||
to_write - bytes_written, file_.get());
|
||||
if (not feof(file_.get()) && ferror(file_.get())) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to write file bytes",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
if (written == 0U) {
|
||||
break;
|
||||
}
|
||||
|
||||
bytes_written += written;
|
||||
}
|
||||
|
||||
flush();
|
||||
|
||||
if (total_written != nullptr) {
|
||||
(*total_written) = bytes_written;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto file::size() const -> std::optional<std::uint64_t> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (file_) {
|
||||
if (fseeko(file_.get(), 0, SEEK_END) == -1) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to seek",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
auto size = ftello(file_.get());
|
||||
if (size == -1) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get position",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
return static_cast<std::uint64_t>(size);
|
||||
}
|
||||
|
||||
std::uint64_t size{};
|
||||
if (not get_file_size(path_, size)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get file size",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
return size;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
} // namespace fifthgrid::utils::file
|
||||
789
support/src/utils/file_smb_directory.cpp
Normal file
789
support/src/utils/file_smb_directory.cpp
Normal file
@@ -0,0 +1,789 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(PROJECT_ENABLE_LIBDSM)
|
||||
|
||||
#include "utils/file_smb_directory.hpp"
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/unix.hpp"
|
||||
#include "utils/windows.hpp"
|
||||
|
||||
namespace fifthgrid::utils::file {
|
||||
auto smb_directory::open(std::string_view host, std::string_view user,
|
||||
std::string_view password, std::string_view path,
|
||||
stop_type *stop_requested) -> smb_directory_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
smb_session_t session{
|
||||
smb_session_new(),
|
||||
smb_session_deleter,
|
||||
};
|
||||
netbios_ns_t ns{
|
||||
netbios_ns_new(),
|
||||
netbios_ns_deleter(),
|
||||
};
|
||||
|
||||
sockaddr_in addr{};
|
||||
|
||||
auto res = netbios_ns_resolve(
|
||||
ns.get(), std::string{host}.c_str(), NETBIOS_FILESERVER,
|
||||
reinterpret_cast<std::uint32_t *>(&addr.sin_addr.s_addr));
|
||||
if (res != DSM_SUCCESS) {
|
||||
res = inet_pton(AF_INET, std::string{host}.c_str(), &addr.sin_addr);
|
||||
if (res != 1) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to resolve host",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
host,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res = smb_session_connect(session.get(), std::string{host}.c_str(),
|
||||
static_cast<std::uint32_t>(addr.sin_addr.s_addr),
|
||||
SMB_TRANSPORT_TCP);
|
||||
if (res != DSM_SUCCESS) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to connect to host",
|
||||
std::to_string(res),
|
||||
host,
|
||||
});
|
||||
}
|
||||
|
||||
smb_session_set_creds(session.get(), std::string{host}.c_str(),
|
||||
std::string{user}.c_str(),
|
||||
std::string{password}.c_str());
|
||||
res = smb_session_login(session.get());
|
||||
if (res != DSM_SUCCESS) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to logon to host",
|
||||
std::to_string(res),
|
||||
host,
|
||||
user,
|
||||
});
|
||||
}
|
||||
|
||||
auto share_name = utils::string::split(path, '/', false).at(0U);
|
||||
|
||||
smb_tid tid{};
|
||||
res = smb_tree_connect(session.get(), share_name.c_str(), &tid);
|
||||
if (res != DSM_SUCCESS) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to connect to share",
|
||||
std::to_string(res),
|
||||
share_name,
|
||||
});
|
||||
}
|
||||
|
||||
return smb_directory_t{
|
||||
new smb_directory{
|
||||
"//" + std::string{host} + "/" + std::string{path},
|
||||
session,
|
||||
share_name,
|
||||
tid,
|
||||
stop_requested,
|
||||
},
|
||||
};
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto smb_directory::open(std::wstring_view host, std::wstring_view user,
|
||||
std::wstring_view password, std::wstring_view path,
|
||||
stop_type *stop_requested) -> smb_directory_t {
|
||||
return open(utils::string::to_utf8(host), utils::string::to_utf8(user),
|
||||
utils::string::to_utf8(password), utils::string::to_utf8(path),
|
||||
stop_requested);
|
||||
}
|
||||
|
||||
auto smb_directory::copy_to(std::string_view new_path,
|
||||
bool /* overwrite */) const -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
// auto to_path = utils::path::absolute(new_path);
|
||||
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to copy directory",
|
||||
"not implemented",
|
||||
new_path,
|
||||
path_,
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_directory::count(bool recursive) const -> std::uint64_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
smb_stat_list_t list{
|
||||
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()),
|
||||
smb_stat_list_deleter(),
|
||||
};
|
||||
auto count = smb_stat_list_count(list.get());
|
||||
|
||||
if (not recursive) {
|
||||
return count;
|
||||
}
|
||||
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get directory count recursively",
|
||||
"not implemented",
|
||||
utils::string::from_bool(recursive),
|
||||
path_,
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return 0U;
|
||||
}
|
||||
|
||||
auto smb_directory::create_directory(std::string_view path) const
|
||||
-> fs_directory_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
auto dir = get_directory(path);
|
||||
if (dir) {
|
||||
return dir;
|
||||
}
|
||||
|
||||
auto res = smb_directory_create(
|
||||
session_.get(), tid_,
|
||||
smb_create_and_validate_relative_path(path_, path).c_str());
|
||||
if (res != DSM_SUCCESS) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to create directory",
|
||||
std::to_string(res),
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
return get_directory(path);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto smb_directory::create_file(std::string_view file_name,
|
||||
bool read_only) const -> fs_file_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
auto fs_file = get_file(file_name);
|
||||
if (fs_file) {
|
||||
if (not dynamic_cast<smb_file *>(fs_file.get())->open(read_only)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to open existing file",
|
||||
file_name,
|
||||
});
|
||||
}
|
||||
|
||||
return fs_file;
|
||||
}
|
||||
|
||||
auto rel_path = smb_create_and_validate_relative_path(path_, file_name);
|
||||
|
||||
smb_fd fd{};
|
||||
auto res =
|
||||
smb_fopen(session_.get(), tid_, rel_path.c_str(), SMB_MOD_RW, &fd);
|
||||
if (res != DSM_SUCCESS) {
|
||||
return nullptr;
|
||||
}
|
||||
smb_fclose(session_.get(), fd);
|
||||
|
||||
res = smb_fopen(session_.get(), tid_, rel_path.c_str(),
|
||||
read_only ? SMB_MOD_RO : SMB_MOD_RW2, &fd);
|
||||
if (res != DSM_SUCCESS) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<smb_file>(
|
||||
fd, smb_create_smb_path(path_, std::string{rel_path}), session_,
|
||||
share_name_, tid_);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
auto smb_directory::exists() const -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
smb_stat_t st{
|
||||
smb_fstat(session_.get(), tid_,
|
||||
smb_create_relative_path(path_).c_str()),
|
||||
smb_stat_deleter(),
|
||||
};
|
||||
if (not st) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_directory::get_directory(std::string_view path) const
|
||||
-> fs_directory_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
auto rel_path = smb_create_and_validate_relative_path(path_, path);
|
||||
smb_stat_t st{
|
||||
smb_fstat(session_.get(), tid_, rel_path.c_str()),
|
||||
smb_stat_deleter(),
|
||||
};
|
||||
if (not st) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to stat directory",
|
||||
rel_path,
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
bool is_dir{smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U};
|
||||
if (not is_dir) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"path is not a directory",
|
||||
rel_path,
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
return smb_directory_t{
|
||||
new smb_directory{
|
||||
smb_create_smb_path(path_, rel_path),
|
||||
session_,
|
||||
share_name_,
|
||||
tid_,
|
||||
stop_requested_,
|
||||
},
|
||||
};
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto smb_directory::get_directories() const -> std::vector<fs_directory_t> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
smb_stat_list_t list{
|
||||
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()),
|
||||
|
||||
smb_stat_list_deleter(),
|
||||
};
|
||||
if (not list) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get directory list",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<fs_directory_t> ret{};
|
||||
|
||||
auto count = smb_stat_list_count(list.get());
|
||||
for (std::size_t idx = 0U; !is_stop_requested() && idx < count; ++idx) {
|
||||
auto *item_st = smb_stat_list_at(list.get(), idx);
|
||||
|
||||
bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U};
|
||||
if (not is_dir) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string name{smb_stat_name(item_st)};
|
||||
if (name == "." || name == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
ret.emplace_back(smb_directory_t{
|
||||
new smb_directory{
|
||||
smb_create_smb_path(path_, name),
|
||||
session_,
|
||||
share_name_,
|
||||
tid_,
|
||||
stop_requested_,
|
||||
},
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto smb_directory::get_file(std::string_view path) const -> fs_file_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
auto rel_path = smb_create_and_validate_relative_path(path_, path);
|
||||
smb_stat_t st{
|
||||
smb_fstat(session_.get(), tid_, rel_path.c_str()),
|
||||
smb_stat_deleter(),
|
||||
};
|
||||
if (not st) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to stat file",
|
||||
rel_path,
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
bool is_dir{smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U};
|
||||
if (is_dir) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"path is not a file",
|
||||
rel_path,
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
return std::make_unique<smb_file>(
|
||||
std::nullopt, smb_create_smb_path(path_, std::string{rel_path}),
|
||||
session_, share_name_, tid_);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto smb_directory::get_files() const -> std::vector<fs_file_t> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
smb_stat_list_t list{
|
||||
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()),
|
||||
smb_stat_list_deleter(),
|
||||
};
|
||||
if (not list) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get file list",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<fs_file_t> ret{};
|
||||
|
||||
auto count = smb_stat_list_count(list.get());
|
||||
for (std::size_t idx = 0U; !is_stop_requested() && idx < count; ++idx) {
|
||||
auto *item_st = smb_stat_list_at(list.get(), idx);
|
||||
|
||||
bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U};
|
||||
if (is_dir) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
std::string name{smb_stat_name(item_st)};
|
||||
ret.emplace_back(std::make_unique<smb_file>(
|
||||
std::nullopt, smb_create_smb_path(path_, name), session_,
|
||||
share_name_, tid_));
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto smb_directory::get_items() const -> std::vector<fs_item_t> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
smb_stat_list_t list{
|
||||
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()),
|
||||
smb_stat_list_deleter(),
|
||||
};
|
||||
if (not list) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get item list",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
std::vector<fs_item_t> ret{};
|
||||
|
||||
auto count = smb_stat_list_count(list.get());
|
||||
for (std::size_t idx = 0U; !is_stop_requested() && idx < count; ++idx) {
|
||||
auto *item_st = smb_stat_list_at(list.get(), idx);
|
||||
|
||||
bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U};
|
||||
std::string name{smb_stat_name(item_st)};
|
||||
|
||||
if (is_dir) {
|
||||
if (name == "." || name == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
ret.emplace_back(smb_directory_t{
|
||||
new smb_directory{
|
||||
path_ + '/' + name,
|
||||
session_,
|
||||
share_name_,
|
||||
tid_,
|
||||
stop_requested_,
|
||||
},
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
ret.emplace_back(std::make_unique<smb_file>(
|
||||
std::nullopt, smb_create_smb_path(path_, name), session_,
|
||||
share_name_, tid_));
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto smb_directory::get_time(time_type type) const
|
||||
-> std::optional<std::uint64_t> {
|
||||
return smb_file::get_time(session_.get(), tid_, path_, type);
|
||||
}
|
||||
|
||||
auto smb_directory::is_stop_requested() const -> bool {
|
||||
return (stop_requested_ != nullptr) && *stop_requested_;
|
||||
}
|
||||
|
||||
auto smb_directory::is_symlink() const -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_directory::move_to(std::string_view new_path) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move directory",
|
||||
"not implemented",
|
||||
new_path,
|
||||
path_,
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_directory::remove() -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
return utils::retry_action([this]() -> bool {
|
||||
if (not exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
auto res = smb_directory_rm(session_.get(), tid_,
|
||||
smb_create_relative_path(path_).c_str());
|
||||
if (res != DSM_SUCCESS) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to remove directory",
|
||||
std::to_string(res),
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_directory::remove_recursively() -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
if (not exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to remove directory recursively",
|
||||
"not implemented",
|
||||
path_,
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_directory::size(bool /* recursive */) const -> std::uint64_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get directory size",
|
||||
"not implemented",
|
||||
path_,
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace fifthgrid::utils::file
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_LIBDSM)
|
||||
549
support/src/utils/file_smb_file.cpp
Normal file
549
support/src/utils/file_smb_file.cpp
Normal file
@@ -0,0 +1,549 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(PROJECT_ENABLE_LIBDSM)
|
||||
|
||||
#include "utils/file_smb_file.hpp"
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace fifthgrid::utils::file {
|
||||
void smb_file::close() {
|
||||
if (fd_.has_value()) {
|
||||
smb_fclose(session_.get(), *fd_);
|
||||
fd_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
auto smb_file::copy_to(std::string_view new_path,
|
||||
bool overwrite) const -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
// auto to_path = utils::path::absolute(new_path);
|
||||
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to copy file",
|
||||
"not implemented",
|
||||
std::to_string(overwrite),
|
||||
new_path,
|
||||
path_,
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_file::exists() const -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
smb_stat_t st{
|
||||
smb_fstat(session_.get(), tid_,
|
||||
smb_create_relative_path(path_).c_str()),
|
||||
smb_stat_deleter(),
|
||||
};
|
||||
if (not st) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return smb_stat_get(st.get(), SMB_STAT_ISDIR) == 0U;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void smb_file::flush() const {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to flush file",
|
||||
"not implemented",
|
||||
path_,
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
}
|
||||
|
||||
auto smb_file::get_time(smb_session *session, smb_tid tid, std::string path,
|
||||
time_type type) -> std::optional<std::uint64_t> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (session == nullptr) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
auto rel_path = smb_create_relative_path(path);
|
||||
smb_stat_t st{
|
||||
smb_fstat(session, tid, rel_path.c_str()),
|
||||
smb_stat_deleter(),
|
||||
};
|
||||
if (not st) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to stat file",
|
||||
"not implemented",
|
||||
rel_path,
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case time_type::accessed:
|
||||
return smb_stat_get(st.get(), SMB_STAT_ATIME);
|
||||
|
||||
case time_type::created:
|
||||
return smb_stat_get(st.get(), SMB_STAT_CTIME);
|
||||
|
||||
case time_type::modified:
|
||||
return smb_stat_get(st.get(), SMB_STAT_MTIME);
|
||||
|
||||
case time_type::written:
|
||||
return smb_stat_get(st.get(), SMB_STAT_WTIME);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto smb_file::is_symlink() const -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_file::move_to(std::string_view new_path) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (utils::string::begins_with(new_path, "//")) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move file",
|
||||
"new path must be in same share",
|
||||
new_path,
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
auto from_path = smb_create_relative_path(path_);
|
||||
auto to_path = smb_create_and_validate_relative_path(
|
||||
utils::string::begins_with(new_path, "/") ? smb_get_root_path(path_)
|
||||
: smb_get_parent_path(path_),
|
||||
new_path);
|
||||
|
||||
auto was_open{false};
|
||||
if (fd_.has_value()) {
|
||||
close();
|
||||
was_open = true;
|
||||
}
|
||||
|
||||
auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_);
|
||||
if (res != DSM_SUCCESS) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to connect to share",
|
||||
std::to_string(res),
|
||||
share_name_,
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
res = smb_file_mv(session_.get(), tid_, from_path.c_str(), to_path.c_str());
|
||||
if (res != DSM_SUCCESS) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move file",
|
||||
std::to_string(res),
|
||||
from_path,
|
||||
to_path,
|
||||
});
|
||||
}
|
||||
|
||||
path_ = smb_create_smb_path(path_, to_path);
|
||||
if (was_open) {
|
||||
return open(read_only_);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_file::open(bool read_only) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (fd_.has_value()) {
|
||||
if (read_only == read_only_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
close();
|
||||
}
|
||||
|
||||
auto rel_path = smb_create_relative_path(path_);
|
||||
|
||||
auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_);
|
||||
if (res != DSM_SUCCESS) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to connect to share",
|
||||
std::to_string(res),
|
||||
share_name_,
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
smb_fd fd{};
|
||||
res = smb_fopen(session_.get(), tid_, rel_path.c_str(),
|
||||
read_only ? SMB_MOD_RO : SMB_MOD_RW2, &fd);
|
||||
if (res != DSM_SUCCESS) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to open file",
|
||||
std::to_string(res),
|
||||
utils::string::from_bool(read_only),
|
||||
rel_path,
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
fd_ = fd;
|
||||
read_only_ = read_only;
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_file::read(unsigned char *data, std::size_t to_read,
|
||||
std::uint64_t offset, std::size_t *total_read) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (total_read != nullptr) {
|
||||
(*total_read) = 0U;
|
||||
}
|
||||
|
||||
if (not fd_.has_value()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to read file",
|
||||
"file not open",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
auto res = smb_fseek(session_.get(), *fd_, static_cast<off_t>(offset),
|
||||
SMB_SEEK_SET);
|
||||
if (res == -1) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to seek file",
|
||||
std::to_string(res),
|
||||
std::to_string(offset),
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t bytes_read{0U};
|
||||
while (bytes_read != to_read) {
|
||||
res = smb_fread(session_.get(), *fd_, &data[bytes_read],
|
||||
to_read - bytes_read);
|
||||
if (res == -1) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to read file",
|
||||
std::to_string(res),
|
||||
std::to_string(offset),
|
||||
std::to_string(to_read),
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
if (res == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
bytes_read += static_cast<std::size_t>(res);
|
||||
}
|
||||
|
||||
if (total_read != nullptr) {
|
||||
(*total_read) = bytes_read;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_file::remove() -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
close();
|
||||
|
||||
return utils::retry_action([this]() -> bool {
|
||||
if (not exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_);
|
||||
if (res != DSM_SUCCESS) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to connect to share",
|
||||
std::to_string(res),
|
||||
share_name_,
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
auto rel_path = smb_create_relative_path(path_);
|
||||
res = smb_file_rm(session_.get(), tid_, rel_path.c_str());
|
||||
if (res != DSM_SUCCESS) {
|
||||
throw utils::error::create_exception(
|
||||
function_name,
|
||||
{
|
||||
"failed to remove file",
|
||||
std::to_string(res),
|
||||
std::to_string(smb_session_get_nt_status(session_.get())),
|
||||
rel_path,
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_file::size() const -> std::optional<std::uint64_t> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (not session_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"session not found",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
auto rel_path = smb_create_relative_path(path_);
|
||||
smb_stat_t st{
|
||||
smb_fstat(session_.get(), tid_, rel_path.c_str()),
|
||||
smb_stat_deleter(),
|
||||
};
|
||||
if (not st) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to stat directory",
|
||||
rel_path,
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
return smb_stat_get(st.get(), SMB_STAT_SIZE);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto smb_file::truncate(std::size_t size) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to truncate file",
|
||||
"not implemented",
|
||||
std::to_string(size),
|
||||
path_,
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto smb_file::write(const unsigned char *data, std::size_t to_write,
|
||||
std::size_t offset, std::size_t *total_written) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
if (total_written != nullptr) {
|
||||
(*total_written) = 0U;
|
||||
}
|
||||
|
||||
if (not fd_.has_value()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to write file",
|
||||
"file not open",
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
auto res = smb_fseek(session_.get(), *fd_, static_cast<off_t>(offset),
|
||||
SMB_SEEK_SET);
|
||||
if (res == -1) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to seek file",
|
||||
std::to_string(res),
|
||||
std::to_string(offset),
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t bytes_written{0U};
|
||||
while (bytes_written != to_write) {
|
||||
res = smb_fwrite(session_.get(), *fd_,
|
||||
const_cast<unsigned char *>(&data[bytes_written]),
|
||||
to_write - bytes_written);
|
||||
if (res == -1) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to write file",
|
||||
std::to_string(res),
|
||||
std::to_string(offset),
|
||||
std::to_string(to_write),
|
||||
path_,
|
||||
});
|
||||
}
|
||||
|
||||
if (res == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
bytes_written += static_cast<std::size_t>(res);
|
||||
}
|
||||
|
||||
if (total_written != nullptr) {
|
||||
(*total_written) = bytes_written;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace fifthgrid::utils::file
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_LIBDSM)
|
||||
198
support/src/utils/file_thread_file.cpp
Normal file
198
support/src/utils/file_thread_file.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "utils/file_thread_file.hpp"
|
||||
|
||||
namespace fifthgrid::utils::file {
|
||||
// auto thread_file::attach_file(native_handle handle,
|
||||
// bool read_only) -> fs_file_t {}
|
||||
|
||||
auto thread_file::attach_file(fs_file_t file) -> fs_file_t {
|
||||
return fs_file_t{
|
||||
new thread_file(std::move(file)),
|
||||
};
|
||||
}
|
||||
|
||||
auto thread_file::open_file(std::string_view path,
|
||||
bool read_only) -> fs_file_t {
|
||||
return fs_file_t{
|
||||
new thread_file(file::open_file(path, read_only)),
|
||||
};
|
||||
}
|
||||
|
||||
auto thread_file::open_or_create_file(std::string_view path,
|
||||
bool read_only) -> fs_file_t {
|
||||
return fs_file_t{
|
||||
new thread_file(file::open_or_create_file(path, read_only)),
|
||||
};
|
||||
}
|
||||
|
||||
void thread_file::io_item::done(bool result) {
|
||||
unique_mutex_lock lock(*mtx);
|
||||
complete = true;
|
||||
success = result;
|
||||
notify->notify_all();
|
||||
}
|
||||
|
||||
void thread_file::io_item::wait() const {
|
||||
if (complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
unique_mutex_lock lock(*mtx);
|
||||
while (not complete) {
|
||||
notify->wait(lock);
|
||||
}
|
||||
notify->notify_all();
|
||||
}
|
||||
|
||||
thread_file::thread_file(std::string_view path) : file_(new file(path)) {}
|
||||
|
||||
thread_file::thread_file(std::wstring_view path)
|
||||
: file_(new file(utils::string::to_utf8(path))) {}
|
||||
|
||||
thread_file::thread_file(fs_file_t file) : file_(std::move(file)) {}
|
||||
|
||||
thread_file::~thread_file() {
|
||||
close();
|
||||
|
||||
if (io_thread_) {
|
||||
io_thread_->join();
|
||||
}
|
||||
}
|
||||
|
||||
void thread_file::close() {
|
||||
do_io([this]() -> bool {
|
||||
file_->close();
|
||||
stop_requested_ = true;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
auto thread_file::copy_to(std::string_view new_path,
|
||||
bool overwrite) const -> bool {
|
||||
return do_io([this, &new_path, &overwrite]() -> bool {
|
||||
return file_->copy_to(new_path, overwrite);
|
||||
});
|
||||
}
|
||||
|
||||
auto thread_file::do_io(action_t action) const -> bool {
|
||||
unique_mutex_lock lock(*mtx_);
|
||||
if (stop_requested_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not io_thread_) {
|
||||
io_thread_ = std::make_unique<std::thread>([this]() { thread_func(); });
|
||||
}
|
||||
|
||||
auto item = std::make_shared<io_item>(action);
|
||||
actions_.emplace_back(item);
|
||||
notify_->notify_all();
|
||||
lock.unlock();
|
||||
|
||||
item->wait();
|
||||
|
||||
return item->success;
|
||||
}
|
||||
|
||||
void thread_file::flush() const {
|
||||
do_io([this]() -> bool {
|
||||
file_->flush();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
auto thread_file::move_to(std::string_view path) -> bool {
|
||||
return do_io([this, &path]() -> bool { return file_->move_to(path); });
|
||||
}
|
||||
|
||||
auto thread_file::read(unsigned char *data, std::size_t to_read,
|
||||
std::uint64_t offset, std::size_t *total_read) -> bool {
|
||||
return do_io([this, &data, &to_read, &offset, &total_read]() -> bool {
|
||||
return file_->read(data, to_read, offset, total_read);
|
||||
});
|
||||
}
|
||||
|
||||
auto thread_file::remove() -> bool {
|
||||
return do_io([this]() -> bool { return file_->remove(); });
|
||||
}
|
||||
|
||||
void thread_file::thread_func() const {
|
||||
unique_mutex_lock lock(*mtx_);
|
||||
notify_->notify_all();
|
||||
lock.unlock();
|
||||
|
||||
const auto run_actions = [this, &lock]() {
|
||||
auto actions = actions_;
|
||||
actions_.clear();
|
||||
notify_->notify_all();
|
||||
lock.unlock();
|
||||
|
||||
for (auto &&action : actions) {
|
||||
action->done(action->action());
|
||||
}
|
||||
};
|
||||
|
||||
while (not stop_requested_) {
|
||||
lock.lock();
|
||||
if (stop_requested_) {
|
||||
lock.unlock();
|
||||
break;
|
||||
}
|
||||
|
||||
while (not stop_requested_ && actions_.empty()) {
|
||||
notify_->wait(lock);
|
||||
}
|
||||
|
||||
if (stop_requested_) {
|
||||
lock.unlock();
|
||||
break;
|
||||
}
|
||||
|
||||
run_actions();
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
run_actions();
|
||||
}
|
||||
|
||||
auto thread_file::truncate(std::size_t size) -> bool {
|
||||
return do_io([this, &size]() -> bool { return file_->truncate(size); });
|
||||
}
|
||||
|
||||
auto thread_file::write(const unsigned char *data, std::size_t to_write,
|
||||
std::size_t offset,
|
||||
std::size_t *total_written) -> bool {
|
||||
return do_io([this, &data, &to_write, &offset, &total_written]() -> bool {
|
||||
return file_->write(data, to_write, offset, total_written);
|
||||
});
|
||||
}
|
||||
|
||||
auto thread_file::size() const -> std::optional<std::uint64_t> {
|
||||
std::optional<std::uint64_t> size;
|
||||
do_io([this, &size]() -> bool {
|
||||
size = file_->size();
|
||||
return size.has_value();
|
||||
});
|
||||
return size;
|
||||
}
|
||||
} // namespace fifthgrid::utils::file
|
||||
240
support/src/utils/hash.cpp
Normal file
240
support/src/utils/hash.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(PROJECT_ENABLE_LIBSODIUM)
|
||||
|
||||
#include "utils/hash.hpp"
|
||||
|
||||
#include "utils/error.hpp"
|
||||
|
||||
namespace fifthgrid::utils::hash {
|
||||
auto create_hash_blake2b_32(std::string_view data) -> hash_32_t {
|
||||
return create_hash_blake2b_t<hash_32_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_32(std::wstring_view data) -> hash_32_t {
|
||||
return create_hash_blake2b_t<hash_32_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_32(const data_buffer &data) -> hash_32_t {
|
||||
return create_hash_blake2b_t<hash_32_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_64(std::string_view data) -> hash_64_t {
|
||||
return create_hash_blake2b_t<hash_64_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_64(std::wstring_view data) -> hash_64_t {
|
||||
return create_hash_blake2b_t<hash_64_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_64(const data_buffer &data) -> hash_64_t {
|
||||
return create_hash_blake2b_t<hash_64_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_128(std::string_view data) -> hash_128_t {
|
||||
return create_hash_blake2b_t<hash_128_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_128(std::wstring_view data) -> hash_128_t {
|
||||
return create_hash_blake2b_t<hash_128_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_128(const data_buffer &data) -> hash_128_t {
|
||||
return create_hash_blake2b_t<hash_128_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_256(std::string_view data) -> hash_256_t {
|
||||
return create_hash_blake2b_t<hash_256_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_256(std::wstring_view data) -> hash_256_t {
|
||||
return create_hash_blake2b_t<hash_256_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_256(const data_buffer &data) -> hash_256_t {
|
||||
return create_hash_blake2b_t<hash_256_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_384(std::string_view data) -> hash_384_t {
|
||||
return create_hash_blake2b_t<hash_384_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_384(std::wstring_view data) -> hash_384_t {
|
||||
return create_hash_blake2b_t<hash_384_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_384(const data_buffer &data) -> hash_384_t {
|
||||
return create_hash_blake2b_t<hash_384_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_512(std::string_view data) -> hash_512_t {
|
||||
return create_hash_blake2b_t<hash_512_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_512(std::wstring_view data) -> hash_512_t {
|
||||
return create_hash_blake2b_t<hash_512_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_512(const data_buffer &data) -> hash_512_t {
|
||||
return create_hash_blake2b_t<hash_512_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_sha256(std::string_view data) -> hash_256_t {
|
||||
return create_hash_sha256(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_sha256(std::wstring_view data) -> hash_256_t {
|
||||
return create_hash_sha256(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_sha256(const data_buffer &data) -> hash_256_t {
|
||||
return create_hash_sha256(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_sha512(std::string_view data) -> hash_512_t {
|
||||
return create_hash_sha512(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_sha512(std::wstring_view data) -> hash_512_t {
|
||||
return create_hash_sha512(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_sha512(const data_buffer &data) -> hash_512_t {
|
||||
return create_hash_sha512(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_sha512(const unsigned char *data, std::size_t data_size)
|
||||
-> hash_512_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
hash_512_t hash{};
|
||||
|
||||
crypto_hash_sha512_state state{};
|
||||
auto res = crypto_hash_sha512_init(&state);
|
||||
if (res != 0) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to initialize sha-512",
|
||||
std::to_string(res),
|
||||
});
|
||||
}
|
||||
|
||||
res = crypto_hash_sha512_update(&state, data, data_size);
|
||||
if (res != 0) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to update sha-512",
|
||||
std::to_string(res),
|
||||
});
|
||||
}
|
||||
|
||||
res = crypto_hash_sha512_final(&state, hash.data());
|
||||
if (res != 0) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to finalize sha-512",
|
||||
std::to_string(res),
|
||||
});
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
auto create_hash_sha256(const unsigned char *data, std::size_t data_size)
|
||||
-> hash_256_t {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
hash_256_t hash{};
|
||||
|
||||
crypto_hash_sha256_state state{};
|
||||
auto res = crypto_hash_sha256_init(&state);
|
||||
if (res != 0) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to initialize sha-256",
|
||||
std::to_string(res),
|
||||
});
|
||||
}
|
||||
|
||||
res = crypto_hash_sha256_update(&state, data, data_size);
|
||||
if (res != 0) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to update sha-256",
|
||||
std::to_string(res),
|
||||
});
|
||||
}
|
||||
|
||||
res = crypto_hash_sha256_final(&state, hash.data());
|
||||
if (res != 0) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to finalize sha-256",
|
||||
std::to_string(res),
|
||||
});
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
} // namespace fifthgrid::utils::hash
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
|
||||
315
support/src/utils/path.cpp
Normal file
315
support/src/utils/path.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "utils/path.hpp"
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/unix.hpp"
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] auto resolve(std::string path) -> std::string {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
#if defined(_WIN32)
|
||||
if (fifthgrid::utils::string::contains(path, "~\\")) {
|
||||
fifthgrid::utils::string::replace(path, "~\\", "%USERPROFILE%\\");
|
||||
}
|
||||
|
||||
if (fifthgrid::utils::string::contains(path, "~/")) {
|
||||
fifthgrid::utils::string::replace(path, "~/", "%USERPROFILE%\\");
|
||||
}
|
||||
|
||||
if (fifthgrid::utils::string::contains(path, "%")) {
|
||||
auto size = ::ExpandEnvironmentStringsA(path.c_str(), nullptr, 0);
|
||||
|
||||
std::string dest;
|
||||
dest.resize(size);
|
||||
::ExpandEnvironmentStringsA(path.c_str(), dest.data(),
|
||||
static_cast<DWORD>(dest.size()));
|
||||
path = dest.c_str();
|
||||
}
|
||||
#else // !defined (_WIN32)
|
||||
if (fifthgrid::utils::string::contains(path, "~\\")) {
|
||||
fifthgrid::utils::string::replace(path, "~\\", "~/");
|
||||
}
|
||||
|
||||
if (fifthgrid::utils::string::contains(path, "~/")) {
|
||||
std::string home{};
|
||||
auto res =
|
||||
fifthgrid::utils::use_getpwuid(getuid(), [&home](struct passwd *pw) {
|
||||
home = (pw->pw_dir ? pw->pw_dir : "");
|
||||
if (home.empty() ||
|
||||
((home == fifthgrid::utils::path::slash) && (getuid() != 0))) {
|
||||
home = fifthgrid::utils::path::combine("/home", {pw->pw_name});
|
||||
}
|
||||
});
|
||||
if (not res) {
|
||||
throw fifthgrid::utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to getpwuid",
|
||||
res.reason,
|
||||
});
|
||||
}
|
||||
|
||||
path = fifthgrid::utils::string::replace(path, "~/", home + "/");
|
||||
}
|
||||
#endif // defined (_WIN32)
|
||||
|
||||
return fifthgrid::utils::path::finalize(path);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace fifthgrid::utils::path {
|
||||
auto absolute(std::string_view path) -> std::string {
|
||||
std::string abs_path{path};
|
||||
if (abs_path.empty()) {
|
||||
return abs_path;
|
||||
}
|
||||
|
||||
abs_path = resolve(abs_path);
|
||||
#if defined(_WIN32)
|
||||
if (not utils::string::contains(abs_path, dot)) {
|
||||
return abs_path;
|
||||
}
|
||||
|
||||
std::string temp;
|
||||
temp.resize(fifthgrid::max_path_length + 1U);
|
||||
::GetFullPathNameA(abs_path.c_str(), static_cast<DWORD>(temp.size()),
|
||||
temp.data(), nullptr);
|
||||
#else // !defined(_WIN32)
|
||||
if (not utils::string::contains(abs_path, dot) ||
|
||||
utils::string::begins_with(abs_path, slash)) {
|
||||
return abs_path;
|
||||
}
|
||||
|
||||
auto found{false};
|
||||
std::string tmp{abs_path};
|
||||
do {
|
||||
auto *res = realpath(tmp.c_str(), nullptr);
|
||||
if (res != nullptr) {
|
||||
abs_path =
|
||||
res + std::string{directory_seperator} + abs_path.substr(tmp.size());
|
||||
free(res);
|
||||
found = true;
|
||||
} else if (tmp == dot) {
|
||||
found = true;
|
||||
} else {
|
||||
tmp = dirname(tmp.data());
|
||||
}
|
||||
} while (not found);
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
return finalize(abs_path);
|
||||
}
|
||||
|
||||
auto absolute(std::wstring_view path) -> std::wstring {
|
||||
return utils::string::from_utf8(absolute(utils::string::to_utf8(path)));
|
||||
}
|
||||
|
||||
auto exists(std::string_view path) -> bool {
|
||||
return utils::file::file{path}.exists() ||
|
||||
utils::file::directory{path}.exists();
|
||||
}
|
||||
|
||||
auto exists(std::wstring_view path) -> bool {
|
||||
return exists(utils::string::to_utf8(path));
|
||||
}
|
||||
|
||||
auto find_program_in_path(const std::string &name_without_extension)
|
||||
-> std::string {
|
||||
static std::mutex mtx{};
|
||||
static std::unordered_map<std::string, std::string> found_items{};
|
||||
|
||||
mutex_lock lock(mtx);
|
||||
if (found_items.contains(name_without_extension)) {
|
||||
return found_items.at(name_without_extension);
|
||||
}
|
||||
|
||||
auto path = utils::get_environment_variable("PATH");
|
||||
if (path.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
static constexpr std::array<std::string_view, 4U> extension_list{
|
||||
".bat",
|
||||
".cmd",
|
||||
".exe",
|
||||
".ps1",
|
||||
};
|
||||
static constexpr auto split_char = ';';
|
||||
#else // !defined(_WIN32)
|
||||
static constexpr std::array<std::string_view, 2U> extension_list{
|
||||
"",
|
||||
".sh",
|
||||
};
|
||||
static constexpr auto split_char = ':';
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
auto search_path_list = utils::string::split(path, split_char, false);
|
||||
for (auto &&search_path : search_path_list) {
|
||||
for (auto &&extension : extension_list) {
|
||||
auto exec_path = combine(
|
||||
search_path, {name_without_extension + std::string{extension}});
|
||||
if (utils::file::file(exec_path).exists()) {
|
||||
found_items[name_without_extension] = exec_path;
|
||||
return exec_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
[[nodiscard]] auto
|
||||
find_program_in_path(std::wstring_view name_without_extension) -> std::wstring {
|
||||
return utils::string::from_utf8(
|
||||
find_program_in_path(utils::string::to_utf8(name_without_extension)));
|
||||
}
|
||||
|
||||
auto get_parent_path(std::string_view path) -> std::string {
|
||||
auto abs_path = absolute(path);
|
||||
|
||||
#if defined(_WIN32)
|
||||
::PathRemoveFileSpecA(abs_path.data());
|
||||
abs_path = abs_path.c_str();
|
||||
#else // !defined(_WIN32)
|
||||
abs_path = std::filesystem::path{abs_path}.parent_path().string();
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
return finalize(abs_path);
|
||||
}
|
||||
|
||||
auto get_parent_path(std::wstring_view path) -> std::wstring {
|
||||
return utils::string::from_utf8(
|
||||
get_parent_path(utils::string::to_utf8(path)));
|
||||
}
|
||||
|
||||
auto get_relative_path(std::string_view path, std::string_view root_path)
|
||||
-> std::string {
|
||||
auto abs_path = absolute(path);
|
||||
auto abs_root_path =
|
||||
absolute(root_path) + std::string{get_directory_seperator<char>()};
|
||||
#if defined(_WIN32)
|
||||
if (utils::string::to_lower(abs_path).starts_with(
|
||||
utils::string::to_lower(abs_root_path))) {
|
||||
#else // !defined(_WIN32)
|
||||
if (abs_path.starts_with(abs_root_path)) {
|
||||
#endif // defined(_WIN32)
|
||||
return abs_path.substr(abs_root_path.size());
|
||||
}
|
||||
|
||||
return abs_path;
|
||||
}
|
||||
|
||||
auto get_relative_path(std::wstring_view path, std::wstring_view root_path)
|
||||
-> std::wstring {
|
||||
return utils::string::from_utf8(get_relative_path(
|
||||
utils::string::to_utf8(path), utils::string::to_utf8(root_path)));
|
||||
}
|
||||
|
||||
auto contains_trash_directory(std::string_view path) -> bool {
|
||||
auto parts = utils::string::split(utils::string::to_lower(absolute(path)),
|
||||
get_directory_seperator<char>(), false);
|
||||
|
||||
return std::find_if(parts.begin(), parts.end(), [](auto &&part) -> bool {
|
||||
return utils::string::begins_with(part, ".trash-") ||
|
||||
part == ".trashes" || part == "$recycle.bin";
|
||||
}) != parts.end();
|
||||
}
|
||||
|
||||
auto contains_trash_directory(std::wstring_view path) -> bool {
|
||||
return contains_trash_directory(utils::string::to_utf8(path));
|
||||
}
|
||||
|
||||
auto make_file_uri(std::string_view path) -> std::string {
|
||||
auto abs_path = absolute(path);
|
||||
#if defined(_WIN32)
|
||||
utils::string::replace(abs_path, backslash, slash);
|
||||
abs_path = std::string{slash} + abs_path;
|
||||
#endif // defined(_WIN32)
|
||||
return "file://" + abs_path;
|
||||
}
|
||||
|
||||
auto make_file_uri(std::wstring_view path) -> std::wstring {
|
||||
return utils::string::from_utf8(make_file_uri(utils::string::to_utf8(path)));
|
||||
}
|
||||
|
||||
auto strip_to_file_name(std::string path) -> std::string {
|
||||
if (path == "." || path == "..") {
|
||||
return path;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
return ::PathFindFileNameA(path.c_str());
|
||||
#else // !defined(_WIN32)
|
||||
return utils::string::contains(path, slash) ? basename(path.data()) : path;
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
|
||||
auto strip_to_file_name(std::wstring path) -> std::wstring {
|
||||
return utils::string::from_utf8(
|
||||
strip_to_file_name(utils::string::to_utf8(path)));
|
||||
}
|
||||
|
||||
auto unmake_file_uri(std::string_view uri) -> std::string {
|
||||
static constexpr std::array<std::string_view, 23U> escape_characters = {
|
||||
{
|
||||
" ", "<", ">", "#", "%", "+", "{", "}", "|", "\\", "^", "~",
|
||||
"[", "]", "`", ";", "/", "?", ":", "@", "=", "&", "$",
|
||||
}};
|
||||
static constexpr std::array<std::string_view, 23U>
|
||||
escape_sequences_lower = {{
|
||||
"%20", "%3c", "%3e", "%23", "%25", "%2b", "%7b", "%7d",
|
||||
"%7c", "%5c", "%5e", "%7e", "%5b", "%5d", "%60", "%3b",
|
||||
"%2f", "%3f", "%3a", "%40", "%3d", "%26", "%24",
|
||||
}};
|
||||
static constexpr std::array<std::string_view, 23U>
|
||||
escape_sequences_upper = {{
|
||||
"%20", "%3C", "%3E", "%23", "%25", "%2B", "%7B", "%7D",
|
||||
"%7C", "%5C", "%5E", "%7E", "%5B", "%5D", "%60", "%3B",
|
||||
"%2F", "%3F", "%3A", "%40", "%3D", "%26", "%24",
|
||||
}};
|
||||
|
||||
std::string ret_uri{uri};
|
||||
#if defined(_WIN32)
|
||||
ret_uri = ret_uri.substr(8U);
|
||||
#else
|
||||
ret_uri = ret_uri.substr(7U);
|
||||
#endif
|
||||
|
||||
for (std::size_t idx = 0U; idx < escape_characters.size(); idx++) {
|
||||
utils::string::replace(ret_uri, escape_sequences_lower.at(idx),
|
||||
escape_characters.at(idx));
|
||||
utils::string::replace(ret_uri, escape_sequences_upper.at(idx),
|
||||
escape_characters.at(idx));
|
||||
}
|
||||
|
||||
return absolute(ret_uri);
|
||||
}
|
||||
|
||||
auto unmake_file_uri(std::wstring_view uri) -> std::wstring {
|
||||
return utils::string::from_utf8(unmake_file_uri(utils::string::to_utf8(uri)));
|
||||
}
|
||||
} // namespace fifthgrid::utils::path
|
||||
219
support/src/utils/string.cpp
Normal file
219
support/src/utils/string.cpp
Normal file
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace fifthgrid::utils::string {
|
||||
auto from_bool(bool val) -> std::string {
|
||||
return std::to_string(static_cast<int>(val));
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_BOOST)
|
||||
auto from_dynamic_bitset(const boost::dynamic_bitset<> &bitset) -> std::string {
|
||||
std::stringstream stream;
|
||||
boost::archive::text_oarchive archive(stream);
|
||||
archive << bitset;
|
||||
return stream.str();
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_BOOST)
|
||||
|
||||
auto from_utf8(std::string_view str) -> std::wstring {
|
||||
if (str.empty()) {
|
||||
return L"";
|
||||
}
|
||||
|
||||
std::wstring out;
|
||||
|
||||
const auto *str_ptr = reinterpret_cast<const std::uint8_t *>(str.data());
|
||||
std::int32_t idx{};
|
||||
auto len{static_cast<std::int32_t>(str.size())};
|
||||
|
||||
#if WCHAR_MAX <= 0xFFFF
|
||||
out.reserve((str.size() + 1U) / 2U);
|
||||
while (idx < len) {
|
||||
UChar32 uni_ch{};
|
||||
U8_NEXT(str_ptr, idx, len, uni_ch);
|
||||
if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) {
|
||||
throw std::runtime_error("from_utf8: invalid UTF-8 sequence");
|
||||
}
|
||||
std::array<UChar, 2U> units{};
|
||||
std::int32_t off{};
|
||||
auto err{false};
|
||||
U16_APPEND(units.data(), off, 2, uni_ch, err);
|
||||
if (err || off <= 0) {
|
||||
throw std::runtime_error("from_utf8: U16_APPEND failed");
|
||||
}
|
||||
out.push_back(static_cast<wchar_t>(units[0U]));
|
||||
if (off == 2) {
|
||||
out.push_back(static_cast<wchar_t>(units[1U]));
|
||||
}
|
||||
}
|
||||
#else // WCHAR_MAX > 0xFFFF
|
||||
out.reserve(str.size());
|
||||
while (idx < len) {
|
||||
UChar32 uni_ch{};
|
||||
U8_NEXT(str_ptr, idx, len, uni_ch);
|
||||
if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) {
|
||||
throw std::runtime_error("from_utf8: invalid UTF-8 sequence");
|
||||
}
|
||||
out.push_back(static_cast<wchar_t>(uni_ch));
|
||||
}
|
||||
#endif // WCHAR_MAX <= 0xFFFF
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_SFML)
|
||||
auto replace_sf(sf::String &src, const sf::String &find, const sf::String &with,
|
||||
std::size_t start_pos) -> sf::String & {
|
||||
if (not src.isEmpty() && (start_pos < src.getSize())) {
|
||||
while ((start_pos = src.find(find, start_pos)) != std::string::npos) {
|
||||
src.replace(start_pos, find.getSize(), with);
|
||||
start_pos += with.getSize();
|
||||
}
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
auto split_sf(sf::String str, wchar_t delim, bool should_trim)
|
||||
-> std::vector<sf::String> {
|
||||
auto result = std::views::split(str.toWideString(), delim);
|
||||
|
||||
std::vector<sf::String> ret{};
|
||||
for (auto &&word : result) {
|
||||
auto val = std::wstring{word.begin(), word.end()};
|
||||
if (should_trim) {
|
||||
trim(val);
|
||||
}
|
||||
ret.emplace_back(val);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_SFML)
|
||||
|
||||
auto to_bool(std::string val) -> bool {
|
||||
auto ret = false;
|
||||
|
||||
trim(val);
|
||||
if (is_numeric(val)) {
|
||||
if (contains(val, ".")) {
|
||||
ret = (to_double(val) != 0.0);
|
||||
} else {
|
||||
std::istringstream(val) >> ret;
|
||||
}
|
||||
} else {
|
||||
std::istringstream(to_lower(val)) >> std::boolalpha >> ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto to_double(const std::string &str) -> double { return std::stod(str); }
|
||||
|
||||
#if defined(PROJECT_ENABLE_BOOST)
|
||||
auto to_dynamic_bitset(const std::string &val) -> boost::dynamic_bitset<> {
|
||||
std::stringstream stream(val);
|
||||
boost::dynamic_bitset<> bitset;
|
||||
boost::archive::text_iarchive archive(stream);
|
||||
archive >> bitset;
|
||||
return bitset;
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_BOOST)
|
||||
|
||||
auto to_int32(const std::string &val) -> std::int32_t { return std::stoi(val); }
|
||||
|
||||
auto to_int64(const std::string &val) -> std::int64_t {
|
||||
return std::stoll(val);
|
||||
}
|
||||
|
||||
auto to_size_t(const std::string &val) -> std::size_t {
|
||||
return static_cast<std::size_t>(std::stoull(val));
|
||||
}
|
||||
|
||||
auto to_uint8(const std::string &val) -> std::uint8_t {
|
||||
return static_cast<std::uint8_t>(std::stoul(val));
|
||||
}
|
||||
|
||||
auto to_uint16(const std::string &val) -> std::uint16_t {
|
||||
return static_cast<std::uint16_t>(std::stoul(val));
|
||||
}
|
||||
|
||||
auto to_uint32(const std::string &val) -> std::uint32_t {
|
||||
return static_cast<std::uint32_t>(std::stoul(val));
|
||||
}
|
||||
|
||||
auto to_uint64(const std::string &val) -> std::uint64_t {
|
||||
return std::stoull(val);
|
||||
}
|
||||
|
||||
auto to_utf8(std::string_view str) -> std::string { return std::string{str}; }
|
||||
|
||||
auto to_utf8(std::wstring_view str) -> std::string {
|
||||
if (str.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string out;
|
||||
out.reserve(static_cast<size_t>(str.size()) * 4);
|
||||
|
||||
#if WCHAR_MAX <= 0xFFFF
|
||||
const auto *u16 = reinterpret_cast<const UChar *>(str.data());
|
||||
std::int32_t idx{};
|
||||
auto len{static_cast<int32_t>(str.size())};
|
||||
while (idx < len) {
|
||||
UChar32 uni_ch{};
|
||||
U16_NEXT(u16, idx, len, uni_ch);
|
||||
if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) {
|
||||
throw std::runtime_error("to_utf8: invalid UTF-16 sequence");
|
||||
}
|
||||
std::array<std::uint8_t, U8_MAX_LENGTH> buf{};
|
||||
std::int32_t off{0};
|
||||
auto err{false};
|
||||
U8_APPEND(buf, off, U8_MAX_LENGTH, uni_ch, err);
|
||||
if (err || off <= 0) {
|
||||
throw std::runtime_error("to_utf8: U8_APPEND failed");
|
||||
}
|
||||
out.append(reinterpret_cast<const char *>(buf.data()),
|
||||
static_cast<std::size_t>(off));
|
||||
}
|
||||
#else // WCHAR_MAX > 0xFFFF
|
||||
for (const auto &cur_ch : str) {
|
||||
auto uni_char{static_cast<UChar32>(cur_ch)};
|
||||
if (not U_IS_UNICODE_CHAR(uni_char)) {
|
||||
throw std::runtime_error("to_utf8: invalid Unicode scalar value");
|
||||
}
|
||||
std::array<std::uint8_t, U8_MAX_LENGTH> buf{};
|
||||
std::int32_t off{0};
|
||||
auto err{false};
|
||||
U8_APPEND(buf, off, U8_MAX_LENGTH, uni_char, err);
|
||||
if (err || off <= 0) {
|
||||
throw std::runtime_error("to_utf8: U8_APPEND failed");
|
||||
}
|
||||
out.append(reinterpret_cast<const char *>(buf.data()),
|
||||
static_cast<std::size_t>(off));
|
||||
}
|
||||
#endif // WCHAR_MAX <= 0xFFFF
|
||||
|
||||
return out;
|
||||
}
|
||||
} // namespace fifthgrid::utils::string
|
||||
88
support/src/utils/time.cpp
Normal file
88
support/src/utils/time.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "utils/time.hpp"
|
||||
|
||||
namespace fifthgrid::utils::time {
|
||||
void get_local_time_now(struct tm &local_time) {
|
||||
std::memset(&local_time, 0, sizeof(local_time));
|
||||
|
||||
const auto now =
|
||||
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
#if defined(_WIN32)
|
||||
localtime_s(&local_time, &now);
|
||||
#else // !defined(_WIN32)
|
||||
localtime_r(&now, &local_time);
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
|
||||
auto get_time_now() -> std::uint64_t {
|
||||
return static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count());
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
// https://stackoverflow.com/questions/321849/strptime-equivalent-on-windows
|
||||
auto strptime(const char *s, const char *f, struct tm *tm) -> const char * {
|
||||
std::istringstream input{s};
|
||||
input.imbue(std::locale(setlocale(LC_ALL, nullptr)));
|
||||
input >> std::get_time(tm, f);
|
||||
if (input.fail()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return reinterpret_cast<const char *>(s + input.tellg());
|
||||
}
|
||||
|
||||
// https://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/
|
||||
auto unix_time_to_filetime(std::uint64_t unix_time) -> FILETIME {
|
||||
auto win_time = unix_time_to_windows_time(unix_time);
|
||||
|
||||
FILETIME file_time{};
|
||||
file_time.dwHighDateTime = static_cast<DWORD>(win_time >> 32U);
|
||||
file_time.dwLowDateTime = win_time & 0xFFFFFFFF;
|
||||
return file_time;
|
||||
}
|
||||
|
||||
auto windows_file_time_to_unix_time(FILETIME win_time) -> std::uint64_t {
|
||||
return windows_time_to_unix_time(
|
||||
(static_cast<std::uint64_t>(win_time.dwHighDateTime) << 32ULL) |
|
||||
static_cast<std::uint64_t>(win_time.dwLowDateTime));
|
||||
}
|
||||
|
||||
auto windows_time_t_to_unix_time(__time64_t win_time) -> std::uint64_t {
|
||||
return static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::system_clock::from_time_t(win_time).time_since_epoch())
|
||||
.count());
|
||||
}
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
auto unix_time_to_windows_time(std::uint64_t unix_time) -> std::uint64_t {
|
||||
return (unix_time / WIN32_TIME_NANOS_PER_TICK) + WIN32_TIME_CONVERSION;
|
||||
}
|
||||
|
||||
auto windows_time_to_unix_time(std::uint64_t win_time) -> std::uint64_t {
|
||||
return (win_time - WIN32_TIME_CONVERSION) * WIN32_TIME_NANOS_PER_TICK;
|
||||
}
|
||||
} // namespace fifthgrid::utils::time
|
||||
82
support/src/utils/timeout.cpp
Normal file
82
support/src/utils/timeout.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "utils/timeout.hpp"
|
||||
|
||||
namespace fifthgrid::utils {
|
||||
timeout::timeout(callback_t timeout_callback,
|
||||
std::chrono::system_clock::duration duration)
|
||||
: duration_(duration),
|
||||
timeout_callback_(std::move(timeout_callback)),
|
||||
timeout_killed_(duration <= std::chrono::system_clock::duration::zero()) {
|
||||
if (timeout_killed_) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeout_thread_ = std::make_unique<std::thread>([this]() {
|
||||
std::unique_lock<std::mutex> loc_lock(timeout_mutex_);
|
||||
|
||||
while (not timeout_killed_) {
|
||||
auto res = timeout_notify_.wait_for(loc_lock, duration_);
|
||||
if (res != std::cv_status::timeout) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (timeout_killed_) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeout_killed_ = true;
|
||||
loc_lock.unlock();
|
||||
|
||||
try {
|
||||
timeout_callback_();
|
||||
} catch (...) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
timeout::~timeout() { disable(); }
|
||||
|
||||
void timeout::disable() {
|
||||
unique_mutex_lock lock(timeout_mutex_);
|
||||
std::unique_ptr<std::thread> timeout_thread{nullptr};
|
||||
std::swap(timeout_thread, timeout_thread_);
|
||||
|
||||
if (not timeout_thread) {
|
||||
timeout_notify_.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
timeout_killed_ = true;
|
||||
timeout_notify_.notify_all();
|
||||
lock.unlock();
|
||||
|
||||
timeout_thread->join();
|
||||
}
|
||||
|
||||
void timeout::reset() {
|
||||
mutex_lock lock(timeout_mutex_);
|
||||
timeout_notify_.notify_all();
|
||||
}
|
||||
} // namespace fifthgrid::utils
|
||||
424
support/src/utils/unix.cpp
Normal file
424
support/src/utils/unix.cpp
Normal file
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if !defined(_WIN32)
|
||||
|
||||
#include "utils/unix.hpp"
|
||||
|
||||
#include "utils/collection.hpp"
|
||||
#include "utils/config.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/path.hpp"
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] auto get_group_list(auto *pass) -> std::vector<gid_t> {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
std::vector<gid_t> groups{};
|
||||
#if defined(__APPLE__)
|
||||
constexpr int buffer_count{8};
|
||||
constexpr int max_group_count{1024};
|
||||
groups.resize(buffer_count);
|
||||
|
||||
std::size_t orig_count{0U};
|
||||
while (true) {
|
||||
auto group_count{static_cast<int>(groups.size())};
|
||||
if (group_count > max_group_count) {
|
||||
fifthgrid::utils::error::handle_error(function_name,
|
||||
"group list has too many groups");
|
||||
break;
|
||||
}
|
||||
|
||||
auto res{
|
||||
getgrouplist(pass->pw_name, static_cast<int>(pass->pw_gid),
|
||||
reinterpret_cast<int *>(groups.data()), &group_count),
|
||||
};
|
||||
if (res < 0) {
|
||||
if (orig_count == 0U) {
|
||||
fifthgrid::utils::error::handle_error(
|
||||
function_name, std::string{"failed to get group list|error|"} +
|
||||
std::to_string(errno));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
groups.resize(static_cast<std::size_t>(group_count));
|
||||
if (groups.size() == orig_count) {
|
||||
break;
|
||||
}
|
||||
|
||||
orig_count = groups.size();
|
||||
}
|
||||
#else // !defined(__APPLE__)
|
||||
int group_count{};
|
||||
auto res = getgrouplist(pass->pw_name, pass->pw_gid, nullptr, &group_count);
|
||||
if (res >= 0) {
|
||||
fifthgrid::utils::error::handle_error(
|
||||
function_name, std::string{"failed to get group list count|error|"} +
|
||||
std::to_string(errno));
|
||||
}
|
||||
|
||||
groups.resize(static_cast<std::size_t>(group_count));
|
||||
res = getgrouplist(pass->pw_name, pass->pw_gid, groups.data(), &group_count);
|
||||
if (res >= 0) {
|
||||
fifthgrid::utils::error::handle_error(
|
||||
function_name,
|
||||
std::string{"failed to get group list|error|"} + std::to_string(errno));
|
||||
}
|
||||
#endif // !defined(__APPLE__)
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
[[nodiscard]] auto sanitize_basename(std::string_view app_name) -> std::string {
|
||||
std::string out;
|
||||
out.reserve(app_name.size());
|
||||
for (const auto &cur_ch : app_name) {
|
||||
if ((cur_ch >= 'a' && cur_ch <= 'z') || (cur_ch >= '0' && cur_ch <= '9') ||
|
||||
(cur_ch == '-' || cur_ch == '_')) {
|
||||
out.push_back(cur_ch);
|
||||
} else if (cur_ch >= 'A' && cur_ch <= 'Z') {
|
||||
out.push_back(static_cast<char>(cur_ch - 'A' + 'a'));
|
||||
} else {
|
||||
out.push_back('-'); // replace spaces/symbols
|
||||
}
|
||||
}
|
||||
|
||||
std::string collapsed;
|
||||
collapsed.reserve(out.size());
|
||||
bool prev_dash = false;
|
||||
for (const auto &cur_ch : out) {
|
||||
if (cur_ch == '-') {
|
||||
if (not prev_dash) {
|
||||
collapsed.push_back(cur_ch);
|
||||
}
|
||||
prev_dash = true;
|
||||
} else {
|
||||
collapsed.push_back(cur_ch);
|
||||
prev_dash = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (collapsed.empty()) {
|
||||
collapsed = "app";
|
||||
}
|
||||
return collapsed;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_autostart_dir() -> std::string {
|
||||
auto config = fifthgrid::utils::get_environment_variable("XDG_CONFIG_HOME");
|
||||
if (config.empty()) {
|
||||
config = fifthgrid::utils::path::combine(
|
||||
fifthgrid::utils::get_environment_variable("HOME"), {".config"});
|
||||
}
|
||||
|
||||
return fifthgrid::utils::path::combine(config, {"autostart"});
|
||||
}
|
||||
|
||||
[[nodiscard]] auto desktop_file_path_for(std::string_view app_name)
|
||||
-> std::string {
|
||||
return fifthgrid::utils::path::combine(
|
||||
get_autostart_dir(), {
|
||||
sanitize_basename(app_name) + ".desktop",
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] auto join_args_for_exec(const std::vector<std::string> &args)
|
||||
-> std::string {
|
||||
std::string str;
|
||||
for (const auto &arg : args) {
|
||||
if (not str.empty()) {
|
||||
str += ' ';
|
||||
}
|
||||
|
||||
auto needs_quotes = arg.find_first_of(" \t\"'\\$`") != std::string::npos;
|
||||
if (needs_quotes) {
|
||||
str += '"';
|
||||
for (const auto &cur_ch : arg) {
|
||||
if (cur_ch == '"' || cur_ch == '\\') {
|
||||
str += '\\';
|
||||
}
|
||||
str += cur_ch;
|
||||
}
|
||||
str += '"';
|
||||
} else {
|
||||
str += arg;
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
#endif // defined(__linux__)
|
||||
} // namespace
|
||||
|
||||
namespace fifthgrid::utils {
|
||||
#if !defined(__APPLE__)
|
||||
auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t {
|
||||
return static_cast<std::uint64_t>(thread);
|
||||
}
|
||||
#endif // !defined(__APPLE__)
|
||||
|
||||
#if defined(__linux__)
|
||||
auto create_autostart_entry(const autostart_cfg &cfg, bool overwrite_existing)
|
||||
-> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
auto file = desktop_file_path_for(cfg.app_name);
|
||||
if (utils::file::file{file}.exists() && not overwrite_existing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto dir = get_autostart_dir();
|
||||
if (dir.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not utils::file::directory{dir}.create_directory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto exec_line = cfg.exec_path;
|
||||
if (not cfg.exec_args.empty()) {
|
||||
exec_line += ' ';
|
||||
exec_line += join_args_for_exec(cfg.exec_args);
|
||||
}
|
||||
|
||||
std::ofstream out(file, std::ios::binary | std::ios::trunc);
|
||||
if (not out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out << "[Desktop Entry]\n";
|
||||
out << "Type=Application\n";
|
||||
out << "Version=1.0\n";
|
||||
out << "Name=" << cfg.app_name << "\n";
|
||||
out << "Exec=" << exec_line << "\n";
|
||||
out << "Terminal=" << (cfg.terminal ? "true" : "false") << "\n";
|
||||
|
||||
if (cfg.comment && not cfg.comment->empty()) {
|
||||
out << "Comment=" << *cfg.comment << "\n";
|
||||
}
|
||||
|
||||
if (cfg.icon_path && not cfg.icon_path->empty()) {
|
||||
out << "Icon=" << *cfg.icon_path << "\n";
|
||||
}
|
||||
|
||||
if (not cfg.only_show_in.empty()) {
|
||||
out << "OnlyShowIn=";
|
||||
for (std::size_t idx = 0U; idx < cfg.only_show_in.size(); ++idx) {
|
||||
if (idx != 0U) {
|
||||
out << ';';
|
||||
}
|
||||
out << cfg.only_show_in[idx];
|
||||
}
|
||||
out << ";\n";
|
||||
}
|
||||
|
||||
if (not cfg.enabled) {
|
||||
out << "X-GNOME-Autostart-enabled=false\n";
|
||||
out << "Hidden=true\n";
|
||||
}
|
||||
|
||||
out.flush();
|
||||
if (not out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
chmod(file.c_str(), 0644);
|
||||
#endif // defined(__linux__) || defined(__APPLE__)
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // defined(__linux__)
|
||||
|
||||
auto get_last_error_code() -> int { return errno; }
|
||||
|
||||
auto get_thread_id() -> std::uint64_t {
|
||||
return convert_to_uint64(pthread_self());
|
||||
}
|
||||
|
||||
auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
std::vector<gid_t> groups{};
|
||||
auto res = use_getpwuid(
|
||||
uid, [&groups](struct passwd *pass) { groups = get_group_list(pass); });
|
||||
if (not res) {
|
||||
throw utils::error::create_exception(res.function_name,
|
||||
{"use_getpwuid failed", res.reason});
|
||||
}
|
||||
|
||||
return collection::includes(groups, gid);
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
auto remove_autostart_entry(std::string_view name) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
auto file = desktop_file_path_for(name);
|
||||
if (not utils::file::file{file}.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return utils::file::file{file}.remove();
|
||||
}
|
||||
#endif // defined(__linux__)
|
||||
|
||||
auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
static std::mutex mtx{};
|
||||
mutex_lock lock{mtx};
|
||||
|
||||
auto *temp_pw = getpwuid(uid);
|
||||
if (temp_pw == nullptr) {
|
||||
return {
|
||||
.function_name = std::string{function_name},
|
||||
.ok = false,
|
||||
.reason = "'getpwuid' returned nullptr",
|
||||
};
|
||||
}
|
||||
|
||||
callback(temp_pw);
|
||||
return {
|
||||
.function_name = std::string{function_name},
|
||||
};
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#if defined(PROJECT_ENABLE_PUGIXML)
|
||||
auto generate_launchd_plist(const plist_cfg &cfg, bool overwrite_existing)
|
||||
-> bool {
|
||||
auto file = utils::path::combine(cfg.plist_path, {cfg.label + ".plist"});
|
||||
if (utils::file::file{file}.exists() && not overwrite_existing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
auto decl = doc.append_child(pugi::node_declaration);
|
||||
decl.append_attribute("version") = "1.0";
|
||||
decl.append_attribute("encoding") = "UTF-8";
|
||||
|
||||
auto plist = doc.append_child("plist");
|
||||
plist.append_attribute("version") = "1.0";
|
||||
|
||||
auto dict = plist.append_child("dict");
|
||||
|
||||
dict.append_child("key").text().set("Label");
|
||||
dict.append_child("string").text().set(cfg.label.c_str());
|
||||
|
||||
dict.append_child("key").text().set("ProgramArguments");
|
||||
auto array = dict.append_child("array");
|
||||
for (const auto &arg : cfg.args) {
|
||||
array.append_child("string").text().set(arg.c_str());
|
||||
}
|
||||
|
||||
dict.append_child("key").text().set("EnvironmentVariables");
|
||||
pugi::xml_node env_dict = dict.append_child("dict");
|
||||
if (not utils::get_environment_variable("PROJECT_TEST_CONFIG_DIR").empty()) {
|
||||
env_dict.append_child("key").text().set("PROJECT_TEST_CONFIG_DIR");
|
||||
env_dict.append_child("string").text().set(
|
||||
utils::get_environment_variable("PROJECT_TEST_CONFIG_DIR"));
|
||||
}
|
||||
if (not utils::get_environment_variable("PROJECT_TEST_INPUT_DIR").empty()) {
|
||||
env_dict.append_child("key").text().set("PROJECT_TEST_INPUT_DIR");
|
||||
env_dict.append_child("string").text().set(
|
||||
utils::get_environment_variable("PROJECT_TEST_INPUT_DIR"));
|
||||
}
|
||||
|
||||
dict.append_child("key").text().set("WorkingDirectory");
|
||||
dict.append_child("string").text().set(cfg.working_dir.c_str());
|
||||
|
||||
dict.append_child("key").text().set("KeepAlive");
|
||||
dict.append_child(cfg.keep_alive ? "true" : "false");
|
||||
|
||||
dict.append_child("key").text().set("RunAtLoad");
|
||||
dict.append_child(cfg.run_at_load ? "true" : "false");
|
||||
|
||||
dict.append_child("key").text().set("StandardOutPath");
|
||||
dict.append_child("string").text().set(cfg.stdout_log.c_str());
|
||||
|
||||
dict.append_child("key").text().set("StandardErrorPath");
|
||||
dict.append_child("string").text().set(cfg.stderr_log.c_str());
|
||||
|
||||
return doc.save_file(file.c_str(), " ",
|
||||
pugi::format_indent | pugi::format_write_bom);
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_PUGIXML)
|
||||
|
||||
#if defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
|
||||
auto launchctl_command(std::string_view label, launchctl_type type) -> int {
|
||||
switch (type) {
|
||||
case launchctl_type::bootout:
|
||||
return system(
|
||||
fmt::format("launchctl bootout gui/{} '{}' 1>/dev/null 2>&1", getuid(),
|
||||
utils::path::combine("~",
|
||||
{
|
||||
"/Library/LaunchAgents",
|
||||
fmt::format("{}.plist", label),
|
||||
}))
|
||||
.c_str());
|
||||
|
||||
case launchctl_type::bootstrap:
|
||||
return system(
|
||||
fmt::format("launchctl bootstrap gui/{} '{}' 1>/dev/null 2>&1",
|
||||
getuid(),
|
||||
utils::path::combine("~",
|
||||
{
|
||||
"/Library/LaunchAgents",
|
||||
fmt::format("{}.plist", label),
|
||||
}))
|
||||
.c_str());
|
||||
|
||||
case launchctl_type::kickstart:
|
||||
return system(
|
||||
fmt::format("launchctl kickstart gui/{}/{}", getuid(), label).c_str());
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto remove_launchd_plist(std::string_view plist_path, std::string_view label,
|
||||
bool should_bootout) -> bool {
|
||||
auto file = utils::file::file{
|
||||
utils::path::combine(plist_path, {std::string{label} + ".plist"}),
|
||||
};
|
||||
if (not file.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto res =
|
||||
should_bootout ? launchctl_command(label, launchctl_type::bootout) : 0;
|
||||
if (not file.remove()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return res == 0;
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
|
||||
#endif // defined(__APPLE__)
|
||||
} // namespace fifthgrid::utils
|
||||
|
||||
#endif // !defined(_WIN32)
|
||||
361
support/src/utils/windows.cpp
Normal file
361
support/src/utils/windows.cpp
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
Copyright <2018-2025> <scott.e.graves@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(_WIN32)
|
||||
|
||||
#include "utils/windows.hpp"
|
||||
|
||||
#include "utils/com_init_wrapper.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/path.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr std::array<std::string_view, 26U> drive_letters{
|
||||
"a:", "b:", "c:", "d:", "e:", "f:", "g:", "h:", "i:",
|
||||
"j:", "k:", "l:", "m:", "n:", "o:", "p:", "q:", "r:",
|
||||
"s:", "t:", "u:", "v:", "w:", "x:", "y:", "z:",
|
||||
};
|
||||
}
|
||||
|
||||
namespace fifthgrid::utils {
|
||||
void create_console() {
|
||||
if (::AllocConsole() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *dummy{nullptr};
|
||||
freopen_s(&dummy, "CONOUT$", "w", stdout);
|
||||
freopen_s(&dummy, "CONOUT$", "w", stderr);
|
||||
freopen_s(&dummy, "CONIN$", "r", stdin);
|
||||
std::cout.clear();
|
||||
std::clog.clear();
|
||||
std::cerr.clear();
|
||||
std::cin.clear();
|
||||
|
||||
auto *out_w = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
auto *in_w = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
SetStdHandle(STD_OUTPUT_HANDLE, out_w);
|
||||
SetStdHandle(STD_ERROR_HANDLE, out_w);
|
||||
SetStdHandle(STD_INPUT_HANDLE, in_w);
|
||||
std::wcout.clear();
|
||||
std::wclog.clear();
|
||||
std::wcerr.clear();
|
||||
std::wcin.clear();
|
||||
}
|
||||
|
||||
void free_console() { ::FreeConsole(); }
|
||||
|
||||
auto get_available_drive_letter(char first) -> std::optional<std::string_view> {
|
||||
const auto *begin = std::ranges::find_if(
|
||||
drive_letters, [first](auto &&val) { return val.at(0U) == first; });
|
||||
if (begin == drive_letters.end()) {
|
||||
begin = drive_letters.begin();
|
||||
}
|
||||
|
||||
auto available =
|
||||
std::ranges::find_if(begin, drive_letters.end(), [](auto &&val) -> bool {
|
||||
return not utils::file::directory{utils::path::combine(val, {"\\"})}
|
||||
.exists();
|
||||
});
|
||||
if (available == drive_letters.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return *available;
|
||||
}
|
||||
|
||||
auto get_available_drive_letters(char first) -> std::vector<std::string_view> {
|
||||
const auto *begin =
|
||||
std::ranges::find_if(drive_letters, [first](auto &&val) -> bool {
|
||||
return val.at(0U) == first;
|
||||
});
|
||||
if (begin == drive_letters.end()) {
|
||||
begin = drive_letters.begin();
|
||||
}
|
||||
|
||||
return std::accumulate(
|
||||
begin, drive_letters.end(), std::vector<std::string_view>(),
|
||||
[](auto &&vec, auto &&letter) -> auto {
|
||||
if (utils::file::directory{utils::path::combine(letter, {"\\"})}
|
||||
.exists()) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
vec.emplace_back(letter);
|
||||
return vec;
|
||||
});
|
||||
}
|
||||
|
||||
auto get_last_error_code() -> DWORD { return ::GetLastError(); }
|
||||
|
||||
auto get_local_app_data_directory() -> const std::string & {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
|
||||
|
||||
static std::string app_data = ([]() -> std::string {
|
||||
PWSTR local_app_data{};
|
||||
if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr,
|
||||
&local_app_data))) {
|
||||
auto ret = utils::string::to_utf8(local_app_data);
|
||||
::CoTaskMemFree(local_app_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"unable to detect local application data folder",
|
||||
});
|
||||
})();
|
||||
|
||||
return app_data;
|
||||
}
|
||||
|
||||
auto get_thread_id() -> std::uint64_t {
|
||||
return static_cast<std::uint64_t>(::GetCurrentThreadId());
|
||||
}
|
||||
|
||||
auto is_process_elevated() -> bool {
|
||||
auto ret{false};
|
||||
HANDLE token{INVALID_HANDLE_VALUE};
|
||||
if (::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
|
||||
TOKEN_ELEVATION token_elevation{};
|
||||
DWORD size = sizeof(token_elevation);
|
||||
if (::GetTokenInformation(token, TokenElevation, &token_elevation,
|
||||
sizeof(token_elevation), &size)) {
|
||||
ret = (token_elevation.TokenIsElevated != 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (token != INVALID_HANDLE_VALUE) {
|
||||
::CloseHandle(token);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto run_process_elevated(std::vector<const char *> args) -> int {
|
||||
std::cout << "Elevating Process" << std::endl;
|
||||
std::string parameters{"-hidden"};
|
||||
for (std::size_t idx = 1U; idx < args.size(); idx++) {
|
||||
parameters +=
|
||||
(parameters.empty() ? args.at(idx) : " " + std::string(args.at(idx)));
|
||||
}
|
||||
|
||||
std::string full_path;
|
||||
full_path.resize(fifthgrid::max_path_length + 1);
|
||||
|
||||
if (::GetModuleFileNameA(nullptr, full_path.data(),
|
||||
fifthgrid::max_path_length)) {
|
||||
SHELLEXECUTEINFOA sei{};
|
||||
sei.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
sei.cbSize = sizeof(sei);
|
||||
sei.lpVerb = "runas";
|
||||
sei.lpFile = full_path.c_str();
|
||||
sei.lpParameters = parameters.c_str();
|
||||
sei.hwnd = nullptr;
|
||||
sei.nShow = SW_NORMAL;
|
||||
if (::ShellExecuteExA(&sei)) {
|
||||
::WaitForSingleObject(sei.hProcess, INFINITE);
|
||||
DWORD exit_code{};
|
||||
::GetExitCodeProcess(sei.hProcess, &exit_code);
|
||||
::CloseHandle(sei.hProcess);
|
||||
return static_cast<int>(exit_code);
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<int>(::GetLastError());
|
||||
}
|
||||
|
||||
void set_last_error_code(DWORD error_code) { ::SetLastError(error_code); }
|
||||
|
||||
auto get_startup_folder() -> std::wstring {
|
||||
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
|
||||
|
||||
PWSTR raw{nullptr};
|
||||
auto result = ::SHGetKnownFolderPath(FOLDERID_Startup, 0, nullptr, &raw);
|
||||
if (FAILED(result)) {
|
||||
if (raw != nullptr) {
|
||||
::CoTaskMemFree(raw);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring str{raw};
|
||||
::CoTaskMemFree(raw);
|
||||
return str;
|
||||
}
|
||||
|
||||
auto create_shortcut(const shortcut_cfg &cfg, bool overwrite_existing) -> bool {
|
||||
FIFTHGRID_USES_FUNCTION_NAME();
|
||||
|
||||
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
|
||||
|
||||
const auto hr_hex = [](HRESULT result) -> std::string {
|
||||
std::ostringstream oss;
|
||||
oss << "0x" << std::uppercase << std::hex << std::setw(8)
|
||||
<< std::setfill('0') << static_cast<std::uint32_t>(result);
|
||||
return oss.str();
|
||||
};
|
||||
|
||||
if (cfg.location.empty()) {
|
||||
utils::error::handle_error(function_name, "shortcut location was empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not utils::file::directory{cfg.location}.create_directory()) {
|
||||
utils::error::handle_error(function_name,
|
||||
"failed to create shortcut directory|path|" +
|
||||
utils::string::to_utf8(cfg.location));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto final_name = cfg.shortcut_name.empty()
|
||||
? utils::path::strip_to_file_name(cfg.exe_path)
|
||||
: cfg.shortcut_name;
|
||||
if (not final_name.ends_with(L".lnk")) {
|
||||
final_name += L".lnk";
|
||||
}
|
||||
|
||||
auto lnk_path = utils::path::combine(cfg.location, {final_name});
|
||||
if (utils::file::file{lnk_path}.exists() && not overwrite_existing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
IShellLinkW *psl{nullptr};
|
||||
auto result = ::CoCreateInstance(CLSID_ShellLink, nullptr,
|
||||
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&psl));
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("CoCreateInstance(CLSID_ShellLink) failed: ") +
|
||||
hr_hex(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
result = psl->SetPath(cfg.exe_path.c_str());
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(function_name,
|
||||
std::string("IShellLink::SetPath failed: ") +
|
||||
hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not cfg.arguments.empty()) {
|
||||
result = psl->SetArguments(cfg.arguments.c_str());
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("IShellLink::SetArguments failed: ") + hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (not cfg.working_directory.empty()) {
|
||||
result = psl->SetWorkingDirectory(cfg.working_directory.c_str());
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("IShellLink::SetWorkingDirectory failed: ") +
|
||||
hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result = psl->SetShowCmd(SW_SHOWNORMAL);
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(function_name,
|
||||
std::string("IShellLink::SetShowCmd failed: ") +
|
||||
hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not cfg.icon_path.empty()) {
|
||||
result = psl->SetIconLocation(cfg.icon_path.c_str(), 0);
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("IShellLink::SetIconLocation failed: ") + hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (not utils::file::file{lnk_path}.remove()) {
|
||||
utils::error::handle_error(function_name,
|
||||
"failed to remove existing shortcut|path|" +
|
||||
utils::string::to_utf8(lnk_path));
|
||||
return false;
|
||||
}
|
||||
|
||||
IPersistFile *ppf{nullptr};
|
||||
result = psl->QueryInterface(IID_PPV_ARGS(&ppf));
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("QueryInterface(IPersistFile) failed: ") + hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
result = ppf->Save(lnk_path.c_str(), TRUE);
|
||||
ppf->SaveCompleted(lnk_path.c_str());
|
||||
|
||||
ppf->Release();
|
||||
psl->Release();
|
||||
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(function_name,
|
||||
std::string("IPersistFile::Save failed: ") +
|
||||
hr_hex(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto remove_shortcut(std::wstring shortcut_name, const std::wstring &location)
|
||||
-> bool {
|
||||
if (not shortcut_name.ends_with(L".lnk")) {
|
||||
shortcut_name += L".lnk";
|
||||
}
|
||||
|
||||
auto file = utils::path::combine(location, {shortcut_name});
|
||||
if (not utils::file::file{file}.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return utils::file::file{file}.remove();
|
||||
}
|
||||
} // namespace fifthgrid::utils
|
||||
|
||||
#endif // defined(_WIN32)
|
||||
Reference in New Issue
Block a user