initial changes

This commit is contained in:
2025-02-18 14:31:57 -06:00
parent fff36b65d4
commit 57e31fbe0d
157 changed files with 23815 additions and 0 deletions

BIN
support/3rd_party/cpp-httplib-0.18.1.tar.gz LFS vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
405abd8170f2a446fc8612ac635d0db5947c0d2e156e32603403a4496255ff00 *cpp-httplib-0.18.1.tar.gz

BIN
support/3rd_party/curl-8.11.0.tar.gz LFS vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
5a231145114589491fc52da118f9c7ef8abee885d1cb1ced99c7290e9a352f07 *curl-8.11.0.tar.gz

BIN
support/3rd_party/googletest-1.15.2.tar.gz LFS vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
7b42b4d6ed48810c5362c265a17faebe90dc2373c885e5216439d37927f02926 googletest-1.15.2.tar.gz

BIN
support/3rd_party/json-3.11.3.tar.gz LFS vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
0d8ef5af7f9794e3263480193c491549b2ba6cc74bb018906202ada498a79406 json-3.11.3.tar.gz

BIN
support/3rd_party/mingw64/binutils-2.43.tar.xz LFS vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
b53606f443ac8f01d1d5fc9c39497f2af322d99e14cea5c0b4b124d630379365 binutils-2.43.tar.xz

BIN
support/3rd_party/mingw64/expat-2.6.4.tar.gz LFS vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
372b18f6527d162fa9658f1c74d22a37429b82d822f5a1e1fc7e00f6045a06a2 *expat-2.6.4.tar.gz

BIN
support/3rd_party/mingw64/gcc-14.2.0.tar.gz LFS vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
7d376d445f93126dc545e2c0086d0f647c3094aae081cdb78f42ce2bc25e7293 gcc-14.2.0.tar.gz

Binary file not shown.

View File

@@ -0,0 +1 @@
a2c443404f00098e9e90acf29dc318e049d2dc78d9ae5f46efb261934a730ce2 icu-release-76-1.tar.gz

Binary file not shown.

View File

@@ -0,0 +1 @@
cc41898aac4b6e8dd5cffd7331b9d9515b912df4420a3a612b5ea2955bbeed2f mingw-w64-v12.0.0.tar.bz2

Binary file not shown.

View File

@@ -0,0 +1 @@
6fc69c01688c9458a57eb9a1664c9aba372ccda420a02bf4429fe610e7e7d591 pkg-config-0.29.2.tar.gz

BIN
support/3rd_party/mingw64/zlib-1.3.1.tar.gz LFS vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
17e88863f3600672ab49182f217281b6fc4d3c762bde361935e436a95214d05c zlib-1.3.1.tar.gz

BIN
support/3rd_party/openssl-3.4.0.tar.gz LFS vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
e15dda82fe2fe8139dc2ac21a36d4ca01d5313c75f99f46c4e8a27709b7294bf *openssl-3.4.0.tar.gz

BIN
support/3rd_party/rocksdb-9.7.4.tar.gz LFS vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
9b810c81731835fda0d4bbdb51d3199d901fa4395733ab63752d297da84c5a47 *rocksdb-9.7.4.tar.gz

BIN
support/3rd_party/spdlog-1.15.0.tar.gz LFS vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
9962648c9b4f1a7bbc76fd8d9172555bad1871fdb14ff4f842ef87949682caa5 *spdlog-1.15.0.tar.gz

4509
support/include/backward.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_ALL_HPP_
#define MONITARR_INCLUDE_UTILS_ALL_HPP_
#include "utils/config.hpp"
#include "utils/base64.hpp"
#include "utils/collection.hpp"
#if defined(_WIN32)
#include "utils/com_init_wrapper.hpp"
#endif // defined(_WIN32)
#include "utils/common.hpp"
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_common.hpp"
#include "utils/db/sqlite/db_delete.hpp"
#include "utils/db/sqlite/db_insert.hpp"
#include "utils/db/sqlite/db_select.hpp"
#include "utils/db/sqlite/db_update.hpp"
#endif // defined(PROJECT_ENABLE_SQLITE)
#if defined(PROJECT_ENABLE_LIBSODIUM)
#include "utils/encrypting_reader.hpp"
#include "utils/encryption.hpp"
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
#include "utils/error.hpp"
#include "utils/file.hpp"
#if defined(PROJECT_ENABLE_LIBSODIUM)
#include "utils/hash.hpp"
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
#include "utils/path.hpp"
#include "utils/string.hpp"
#include "utils/time.hpp"
#if !defined(_WIN32)
#include "utils/unix.hpp"
#endif // !defined(_WIN32)
#if defined(_WIN32)
#include "utils/windows.hpp"
#endif // defined(_WIN32)
#endif // MONITARR_INCLUDE_UTILS_ALL_HPP_

View File

@@ -0,0 +1,174 @@
// NOLINTBEGIN
#ifndef _MACARON_BASE64_H_
#define _MACARON_BASE64_H_
/**
* The MIT License (MIT)
* Copyright (c) 2016 tomykaira
*
* 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.
*/
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#endif
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#pragma GCC diagnostic ignored "-Wuseless-cast"
#endif
#include <array>
#include <string>
#include <vector>
namespace macaron::Base64 {
static std::string Encode(const unsigned char *data, std::size_t len) {
static constexpr std::array<unsigned char, 64U> sEncodingTable{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
};
auto in_len{len};
std::string ret;
if (in_len > 0) {
std::size_t out_len{4U * ((in_len + 2U) / 3U)};
ret = std::string(out_len, '\0');
std::size_t i;
auto *p = reinterpret_cast<unsigned char *>(ret.data());
for (i = 0U; i < in_len - 2U; i += 3U) {
*p++ = sEncodingTable[(data[i] >> 2U) & 0x3F];
*p++ = sEncodingTable[((data[i] & 0x3) << 4U) |
((int)(data[i + 1U] & 0xF0) >> 4U)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) |
((int)(data[i + 2U] & 0xC0) >> 6U)];
*p++ = sEncodingTable[data[i + 2U] & 0x3F];
}
if (i < in_len) {
*p++ = sEncodingTable[(data[i] >> 2U) & 0x3F];
if (i == (in_len - 1U)) {
*p++ = sEncodingTable[((data[i] & 0x3) << 4U)];
*p++ = '=';
} else {
*p++ = sEncodingTable[((data[i] & 0x3) << 4U) |
((int)(data[i + 1U] & 0xF0) >> 4U)];
*p++ = sEncodingTable[((data[i + 1U] & 0xF) << 2U)];
}
*p++ = '=';
}
}
return ret;
}
[[maybe_unused]] static std::string Encode(std::string_view data) {
return Encode(reinterpret_cast<const unsigned char *>(data.data()),
data.size());
}
[[maybe_unused]] static std::vector<unsigned char>
Decode(std::string_view input) {
static constexpr std::array<unsigned char, 256> kDecodingTable{
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57,
58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64,
};
std::vector<unsigned char> out;
if (not input.empty()) {
auto in_len{input.size()};
if (in_len % 4U != 0U) {
throw std::runtime_error("Input data size is not a multiple of 4");
}
std::size_t out_len{in_len / 4U * 3U};
if (input[in_len - 1U] == '=') {
out_len--;
}
if (input[in_len - 2U] == '=') {
out_len--;
}
out.resize(out_len);
for (std::size_t i = 0U, j = 0U; i < in_len;) {
std::uint32_t a =
input.at(i) == '='
? 0U & i++
: kDecodingTable[static_cast<unsigned char>(input.at(i++))];
std::uint32_t b =
input.at(i) == '='
? 0U & i++
: kDecodingTable[static_cast<unsigned char>(input.at(i++))];
std::uint32_t c =
input.at(i) == '='
? 0U & i++
: kDecodingTable[static_cast<unsigned char>(input.at(i++))];
std::uint32_t d =
input.at(i) == '='
? 0U & i++
: kDecodingTable[static_cast<unsigned char>(input.at(i++))];
std::uint32_t triple =
(a << 3U * 6U) + (b << 2U * 6U) + (c << 1U * 6U) + (d << 0U * 6U);
if (j < out_len)
out[j++] = (triple >> 2U * 8U) & 0xFF;
if (j < out_len)
out[j++] = (triple >> 1U * 8U) & 0xFF;
if (j < out_len)
out[j++] = (triple >> 0U * 8U) & 0xFF;
}
}
return out;
}
} // namespace macaron::Base64
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#endif /* _MACARON_BASE64_H_ */
// NOLINTEND

View File

@@ -0,0 +1,178 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_COLLECTION_HPP_
#define MONITARR_INCLUDE_UTILS_COLLECTION_HPP_
#include "utils/config.hpp"
#include "utils/error.hpp"
#include "utils/string.hpp"
namespace monitarr::utils::collection {
template <typename col_t>
[[nodiscard]] inline auto excludes(const col_t &collection,
const typename col_t::value_type &val)
-> bool;
template <typename col_t>
[[nodiscard]] inline auto includes(const col_t &collection,
const typename col_t::value_type &val)
-> bool;
template <typename val_t>
[[nodiscard]] inline auto from_hex_string(std::string_view str, val_t &val)
-> bool;
template <typename val_t>
[[nodiscard]] inline auto from_hex_string(std::wstring_view str, val_t &val)
-> bool;
template <typename col_t>
inline auto remove_element(col_t &collection,
const typename col_t::value_type &value) -> col_t &;
template <typename col_t>
[[nodiscard]] inline auto to_hex_string(const col_t &collection) -> std::string;
template <typename col_t>
[[nodiscard]] inline auto to_hex_wstring(const col_t &collection)
-> std::wstring;
template <typename col_t>
inline auto excludes(const col_t &collection,
const typename col_t::value_type &val) -> bool {
return std::find(collection.begin(), collection.end(), val) ==
collection.end();
}
template <typename col_t>
inline auto includes(const col_t &collection,
const typename col_t::value_type &val) -> bool {
return std::find(collection.begin(), collection.end(), val) !=
collection.end();
}
template <typename val_t>
[[nodiscard]] inline auto from_hex_string_t(std::string_view str, val_t &val)
-> bool {
MONITARR_USES_FUNCTION_NAME();
static constexpr const auto base16{16};
try {
val.clear();
std::string fmt_val{str};
utils::string::trim(fmt_val);
if (fmt_val.empty()) {
return true;
}
fmt_val = utils::string::to_lower(fmt_val);
if (utils::string::begins_with(fmt_val, "0x")) {
fmt_val = fmt_val.substr(2U);
}
if (fmt_val.empty()) {
throw utils::error::create_exception(function_name,
{
"hex string is invalid",
str,
});
}
if (fmt_val.length() % 2U) {
fmt_val = '0' + fmt_val;
}
auto iter = std::find_if_not(
fmt_val.begin(), fmt_val.end(), [](auto cur_char) -> bool {
auto check = static_cast<std::uint32_t>(cur_char);
return ((check >= 48U && check <= 57U) ||
(check >= 97U && check <= 102U));
});
if (iter != fmt_val.end()) {
auto invalid_idx{std::distance(fmt_val.begin(), iter)};
throw std::range_error(utils::error::create_error_message({
function_name,
"invalid character in hex string",
std::to_string(invalid_idx),
std::string(1U, str.at(invalid_idx)),
str,
}));
}
val.resize(fmt_val.length() / 2U);
for (std::size_t idx = 0U; idx < fmt_val.length(); idx += 2U) {
val.at(idx / 2U) = static_cast<typename val_t::value_type>(
std::strtoul(fmt_val.substr(idx, 2U).c_str(), nullptr, base16));
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
val.clear();
return false;
}
template <typename val_t>
inline auto from_hex_string(std::string_view str, val_t &val) -> bool {
return from_hex_string_t<val_t>(str, val);
}
template <typename val_t>
inline auto from_hex_string(std::wstring_view str, val_t &val) -> bool {
return from_hex_string_t<val_t>(utils::string::to_utf8(str), val);
}
template <typename col_t>
inline auto remove_element(col_t &collection,
const typename col_t::value_type &value) -> col_t & {
collection.erase(std::remove(collection.begin(), collection.end(), value),
collection.end());
return collection;
}
template <typename col_t>
inline auto to_hex_string(const col_t &collection) -> std::string {
static_assert(sizeof(typename col_t::value_type) == 1U,
"value_type must be 1 byte in size");
static constexpr const auto mask = 0xFF;
std::stringstream stream{};
for (auto &&val : collection) {
stream << std::setfill('0') << std::setw(2) << std::hex
<< (static_cast<std::uint32_t>(val) & mask);
}
return stream.str();
}
template <typename col_t>
inline auto to_hex_wstring(const col_t &collection) -> std::wstring {
return utils::string::from_utf8(to_hex_string<col_t>(collection));
}
} // namespace monitarr::utils::collection
#endif // MONITARR_INCLUDE_UTILS_COLLECTION_HPP_

View File

@@ -0,0 +1,54 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_COM_INIT_WRAPPER_HPP_
#define MONITARR_INCLUDE_UTILS_COM_INIT_WRAPPER_HPP_
#if defined(_WIN32)
#include "utils/config.hpp"
namespace monitarr::utils {
struct com_init_wrapper final {
com_init_wrapper()
: initialized_(
SUCCEEDED(::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) {}
com_init_wrapper(const com_init_wrapper &) = delete;
com_init_wrapper(com_init_wrapper &&) = delete;
~com_init_wrapper() {
if (initialized_) {
::CoUninitialize();
}
}
auto operator=(const com_init_wrapper &) -> com_init_wrapper & = delete;
auto operator=(com_init_wrapper &&) -> com_init_wrapper & = delete;
[[nodiscard]] auto is_initialized() const -> bool { return initialized_; }
private:
BOOL initialized_;
};
} // namespace monitarr::utils
#endif // defined(_WIN32)
#endif // MONITARR_INCLUDE_UTILS_COM_INIT_WRAPPER_HPP_

View File

@@ -0,0 +1,145 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_COMMON_HPP_
#define MONITARR_INCLUDE_UTILS_COMMON_HPP_
#include "utils/config.hpp"
namespace monitarr::utils {
struct result final {
std::string function_name;
bool ok{true};
std::string reason{"success"};
[[nodiscard]] operator bool() const { return ok; }
};
using retryable_action_t = std::function<bool()>;
[[nodiscard]] inline constexpr auto
calculate_read_size(std::uint64_t total_size, std::size_t read_size,
std::uint64_t offset) -> std::size_t {
return static_cast<std::size_t>(
((offset + read_size) > total_size)
? ((offset < total_size) ? total_size - offset : 0U)
: read_size);
}
[[nodiscard]] auto compare_version_strings(std::string version1,
std::string version2)
-> std::int32_t;
[[nodiscard]] auto compare_version_strings(std::wstring_view version1,
std::wstring_view version2)
-> std::int32_t;
#if defined(PROJECT_ENABLE_STDUUID)
[[nodiscard]] auto create_uuid_string() -> std::string;
[[nodiscard]] auto create_uuid_wstring() -> std::wstring;
#endif // defined(PROJECT_ENABLE_STDUUID)
template <typename result_t, typename data_t>
[[nodiscard]] inline constexpr auto divide_with_ceiling(result_t numerator,
data_t denominator)
-> result_t;
template <typename data_t>
[[nodiscard]] inline auto generate_random_between(data_t begin, data_t end)
-> data_t;
[[nodiscard]] auto generate_random_string(std::size_t length) -> std::string;
[[nodiscard]] auto generate_random_wstring(std::size_t length) -> std::wstring;
#if defined(PROJECT_ENABLE_LIBSODIUM)
template <typename data_t>
[[nodiscard]] inline auto generate_secure_random() -> data_t;
template <typename data_t>
[[nodiscard]] inline auto generate_secure_random(std::size_t size) -> data_t;
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
[[nodiscard]] auto get_environment_variable(std::string_view variable)
-> std::string;
[[nodiscard]] auto get_environment_variable(std::wstring_view variable)
-> std::wstring;
#if defined(PROJECT_ENABLE_BOOST)
[[nodiscard]] auto get_next_available_port(std::uint16_t first_port,
std::uint16_t &available_port)
-> bool;
#endif // defined(PROJECT_ENABLE_BOOST)
[[nodiscard]] auto retry_action(
retryable_action_t action, std::size_t retry_count = 200U,
std::chrono::milliseconds retry_wait = std::chrono::milliseconds(10))
-> bool;
template <typename result_t, typename data_t>
inline constexpr auto divide_with_ceiling(result_t numerator,
data_t denominator) -> result_t {
static_assert(std::is_integral_v<std::remove_cv_t<data_t>>,
"denominator must be an integral type");
return denominator == 0
? 0
: (numerator / denominator) + (numerator % denominator != 0);
}
template <typename data_t>
inline auto generate_random_between(data_t begin, data_t end) -> data_t {
static_assert(std::is_integral_v<std::remove_cv_t<data_t>>,
"data_t must be an integral type");
if (end <= begin) {
throw std::range_error("end must be greater than begin");
}
thread_local std::mt19937 gen(
static_cast<unsigned long>(std::time(nullptr) ^ std::random_device{}()));
std::uniform_int_distribution<data_t> dis(begin, end);
return dis(gen);
}
#if defined(PROJECT_ENABLE_LIBSODIUM)
template <typename data_t> inline auto generate_secure_random() -> data_t {
static_assert(!is_collection<std::decay_t<data_t>>::value,
"data_t is a vector or collection");
data_t ret{};
randombytes_buf(&ret, sizeof(ret));
return ret;
}
template <typename data_t>
inline auto generate_secure_random(std::size_t size) -> data_t {
static_assert(is_collection<std::decay_t<data_t>>::value,
"data_t is not a vector or collection");
data_t ret;
ret.resize(size);
randombytes_buf(ret.data(), ret.size() * sizeof(typename data_t::value_type));
return ret;
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
} // namespace monitarr::utils
#endif // MONITARR_INCLUDE_UTILS_COMMON_HPP_

View File

@@ -0,0 +1,482 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_CONFIG_HPP_
#define MONITARR_INCLUDE_UTILS_CONFIG_HPP_
#define NOMINMAX
#if defined(_WIN32)
#define WINVER 0x0602
#define _WIN32_WINNT WINVER
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#if defined(PROJECT_ENABLE_WINFSP)
#include <sddl.h>
#endif // defined(PROJECT_ENABLE_WINFSP)
#include <direct.h>
#if !defined(__cplusplus)
#include <errno.h>
#endif // !defined(__cplusplus)
#include <fcntl.h>
#include <io.h>
#include <iphlpapi.h>
#include <objbase.h>
#include <psapi.h>
#include <rpc.h>
#include <share.h>
#include <shellapi.h>
#include <shlobj.h>
#include <shlwapi.h>
#if !defined(__cplusplus)
#include <stdio.h>
#endif // !defined(__cplusplus)
#include <sys/stat.h>
#include <sys/types.h>
#if !defined(__cplusplus)
#include <time.h>
#endif // !defined(__cplusplus)
#else // !defined(_WIN32)
#include <arpa/inet.h>
#include <dirent.h>
#include <fcntl.h>
#include <grp.h>
#include <libgen.h>
#include <netinet/in.h>
#include <pwd.h>
#include <sys/file.h>
#include <sys/socket.h>
#if defined(__LFS64__)
#include <sys/stat64.h>
#else // !defined(__LFS64__)
#include <sys/stat.h>
#endif // defined(__LFS64__)
#if defined(__linux__)
#include <sys/statfs.h>
#endif // defined(HAS_SETXATTR)
#if defined(HAS_SETXATTR)
#include <sys/types.h>
#include <sys/xattr.h>
#endif // defined(HAS_SETXATTR)
#if defined(__APPLE__)
#include <libproc.h>
#include <sys/attr.h>
#include <sys/mount.h>
#include <sys/statvfs.h>
#include <sys/vnode.h>
#endif // defined(__APPLE__)
#include <unistd.h>
#endif // defined(_WIN32)
#if defined(HAS_WORDEXP_H)
#include <wordexp.h>
#endif // defined(HAS_WORDEXP_H)
#if defined(__cplusplus)
#include <algorithm>
#include <array>
#include <atomic>
#include <bit>
#include <cerrno>
#include <chrono>
#include <ciso646>
#include <climits>
#include <codecvt>
#include <condition_variable>
#include <csignal>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <filesystem>
#include <fstream>
#include <functional>
#include <future>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <limits>
#include <locale>
#include <map>
#include <memory>
#include <mutex>
#include <numeric>
#include <optional>
#include <ostream>
#include <queue>
#include <random>
#include <ranges>
#include <regex>
#include <span>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <thread>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
#endif // defined(__cplusplus)
#if defined(PROJECT_ENABLE_CURL)
#include "curl/curl.h"
#include "curl/multi.h"
#endif // defined(PROJECT_ENABLE_CURL)
#if defined(PROJECT_ENABLE_FUSE)
#if FUSE_USE_VERSION >= 30
#include <fuse.h>
#include <fuse_lowlevel.h>
#else
#include <fuse/fuse.h>
#endif
#endif // defined(PROJECT_ENABLE_FUSE)
#if defined(PROJECT_ENABLE_FZF)
#if defined(__cplusplus)
extern "C" {
#endif // defined(__cplusplus)
#include "fzf.h"
#if defined(__cplusplus)
}
#endif // defined(__cplusplus)
#endif // defined(PROJECT_ENABLE_FZF)
#if defined(PROJECT_ENABLE_OPENSSL)
#include "openssl/ssl.h"
#endif // defined(PROJECT_ENABLE_OPENSSL)
#if defined(PROJECT_ENABLE_LIBDSM)
#if defined(__cplusplus)
extern "C" {
#endif // defined(__cplusplus)
#include "bdsm/bdsm.h"
#if defined(__cplusplus)
}
struct netbios_ns_deleter final {
void operator()(netbios_ns *ns) const {
if (ns != nullptr) {
netbios_ns_destroy(ns);
}
}
};
using netbios_ns_t = std::unique_ptr<netbios_ns, netbios_ns_deleter>;
inline const auto smb_session_deleter = [](smb_session *session) {
if (session != nullptr) {
smb_session_destroy(session);
}
};
using smb_session_t = std::shared_ptr<smb_session>;
struct smb_stat_deleter final {
void operator()(smb_stat st) const {
if (st != nullptr) {
smb_stat_destroy(st);
}
}
};
using smb_stat_t = std::unique_ptr<smb_file, smb_stat_deleter>;
struct smb_stat_list_deleter final {
void operator()(smb_file *list) const {
if (list != nullptr) {
smb_stat_list_destroy(list);
}
}
};
using smb_stat_list_t = std::unique_ptr<smb_file, smb_stat_list_deleter>;
#endif // defined(__cplusplus)
#endif // defined(PROJECT_ENABLE_LIBDSM)
#if defined(PROJECT_ENABLE_LIBEVENT)
#include "event2/buffer.h"
#include "event2/bufferevent.h"
#include "event2/listener.h"
#include "event2/thread.h"
#include "event2/util.h"
#endif // defined(PROJECT_ENABLE_LIBEVENT)
#if defined(PROJECT_ENABLE_LIBSODIUM)
#include "sodium.h"
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
#if defined(PROJECT_ENABLE_SDL)
#include "SDL.h"
#include "SDL_gamecontroller.h"
#include "SDL_joystick.h"
#endif // defined(PROJECT_ENABLE_SDL)
#if defined(PROJECT_ENABLE_SQLITE)
#include "sqlite3.h"
#endif // defined(PROJECT_ENABLE_SQLITE)
#if defined(PROJECT_ENABLE_VLC)
#include <vlc/vlc.h>
#if defined(__cplusplus)
[[nodiscard]] inline auto get_libvlc_error_msg() -> std::string {
const auto *msg = libvlc_errmsg();
return msg == nullptr ? "none" : msg;
}
struct vlc_deleter final {
void operator()(libvlc_instance_t *inst) const {
if (inst != nullptr) {
libvlc_release(inst);
}
}
};
using vlc_t = std::unique_ptr<libvlc_instance_t, vlc_deleter>;
struct vlc_media_deleter final {
void operator()(libvlc_media_t *media) const {
if (media != nullptr) {
libvlc_media_release(media);
}
}
};
using vlc_media_t = std::unique_ptr<libvlc_media_t, vlc_media_deleter>;
struct vlc_media_list_deleter final {
void operator()(libvlc_media_list_t *media_list) const {
if (media_list != nullptr) {
libvlc_media_list_release(media_list);
}
}
};
using vlc_media_list_t =
std::unique_ptr<libvlc_media_list_t, vlc_media_list_deleter>;
struct vlc_string_deleter final {
void operator()(char *str) const {
if (str != nullptr) {
libvlc_free(str);
}
}
};
using vlc_string_t = std::unique_ptr<char, vlc_string_deleter>;
#endif // defined(__cplusplus)
#endif // defined(PROJECT_ENABLE_VLC)
#if !defined(fstat64)
#define fstat64 fstat
#endif // !defined(fstat64)
#if !defined(pread64)
#define pread64 pread
#endif // !defined(pread64)
#if !defined(pwrite64)
#define pwrite64 pwrite
#endif // !defined(pwrite64)
#if !defined(stat64)
#define stat64 stat
#endif // !defined(stat64)
#if !defined(statfs64)
#define statfs64 statfs
#endif // !defined(statfs64)
#if !defined(off64_t)
#define off64_t std::size_t
#endif // !defined(off64_t)
#if !defined(__off64_t)
#define __off64_t off64_t
#endif // !defined(__off64_t)
#if defined(__cplusplus)
#if defined(PROJECT_ENABLE_BOOST)
#include "boost/archive/text_iarchive.hpp"
#include "boost/archive/text_oarchive.hpp"
#include "boost/asio.hpp"
#include "boost/asio/io_context.hpp"
#include "boost/bind/bind.hpp"
#include "boost/dynamic_bitset.hpp"
#include "boost/dynamic_bitset/serialization.hpp"
#include "boost/endian/conversion.hpp"
#include "boost/integer.hpp"
#include "boost/interprocess/sync/named_mutex.hpp"
#include "boost/interprocess/sync/scoped_lock.hpp"
#include "boost/multiprecision/cpp_dec_float.hpp"
#include "boost/multiprecision/cpp_int.hpp"
#include "boost/serialization/vector.hpp"
#endif // defined(PROJECT_ENABLE_BOOST)
#if defined(PROJECT_ENABLE_CLI11)
#if defined(PROJECT_IS_MINGW) && !defined(PROJECT_IS_MINGW_UNIX)
#include "CLI/CLI.hpp"
#else // !defined(PROJECT_IS_MINGW) || defined(PROJECT_IS_MINGW_UNIX)
#include "CLI11.hpp"
#endif // defined(PROJECT_IS_MINGW) && !defined(PROJECT_IS_MINGW_UNIX)
#endif // defined(PROJECT_ENABLE_CLI11)
#if defined(PROJECT_ENABLE_CPP_HTTPLIB)
#include "httplib.h"
#endif // defined(PROJECT_ENABLE_JSON)
#if defined(PROJECT_ENABLE_DTL)
#include "dtl/dtl.hpp"
#endif // defined(PROJECT_ENABLE_DTL)
#if defined(PROJECT_ENABLE_JSON)
#include "json.hpp"
#endif // defined(PROJECT_ENABLE_JSON)
#if defined(PROJECT_ENABLE_NANA)
#include "nana/gui.hpp"
#include "nana/gui/timer.hpp"
#include "nana/gui/widgets/button.hpp"
#include "nana/gui/widgets/combox.hpp"
#include "nana/gui/widgets/group.hpp"
#include "nana/gui/widgets/label.hpp"
#include "nana/gui/widgets/panel.hpp"
#include "nana/gui/widgets/picture.hpp"
#include "nana/gui/widgets/tabbar.hpp"
#endif // defined(PROJECT_ENABLE_NANA)
#if defined(PROJECT_ENABLE_PUGIXML)
#include "pugixml.hpp"
#endif // defined(PROJECT_ENABLE_PUGIXML)
#if defined(PROJECT_ENABLE_ROCKSDB)
#include "rocksdb/db.h"
#include "rocksdb/utilities/transaction_db.h"
#endif // defined(PROJECT_ENABLE_ROCKSDB)
#if defined(PROJECT_ENABLE_SFML)
#include "RoundedRectangleShape.hpp"
#include "SFML/Graphics.hpp"
#include "Text2.hpp"
#endif // defined(PROJECT_ENABLE_SFML)
#if defined(PROJECT_ENABLE_SAGO_PLATFORM_FOLDERS)
#include "platform_folders.hpp"
#endif // defined(PROJECT_ENABLE_SAGO_PLATFORM_FOLDERS)
#if defined(PROJECT_ENABLE_SPDLOG)
#include "spdlog/async.h"
#include "spdlog/fmt/bundled/core.h"
#include "spdlog/fmt/bundled/format.h"
#include "spdlog/fmt/bundled/ranges.h"
#include "spdlog/fmt/chrono.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/spdlog.h"
#endif // defined(PROJECT_ENABLE_SPDLOG)
#if defined(PROJECT_ENABLE_STDUUID)
#include "uuid.h"
#endif // defined(PROJECT_ENABLE_STDUUID)
#if defined(PROJECT_ENABLE_TPL)
#include "process.hpp"
#endif // defined(PROJECT_ENABLE_TPL)
#if defined(PROJECT_ENABLE_WINFSP)
#include "winfsp/winfsp.hpp"
#endif // defined(PROJECT_ENABLE_WINFSP)
namespace monitarr {
using data_buffer = std::vector<unsigned char>;
using mutex_lock = std::lock_guard<std::mutex>;
using recur_mutex_lock = std::lock_guard<std::recursive_mutex>;
using stop_type = std::atomic_bool;
using stop_type_callback = std::function<std::atomic_bool()>;
using unique_mutex_lock = std::unique_lock<std::mutex>;
using unique_recur_mutex_lock = std::unique_lock<std::recursive_mutex>;
#if defined(_WIN32)
#if defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
inline constexpr const auto max_path_length = std::size_t{32767U};
#else // !defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
inline constexpr const auto max_path_length = std::size_t{MAX_PATH};
#endif // defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
using native_handle = HANDLE;
#else // !defined(_WIN32)
inline constexpr const auto max_path_length = std::size_t{PATH_MAX};
using native_handle = int;
#if !defined(INVALID_HANDLE_VALUE)
#define INVALID_HANDLE_VALUE (-1)
#endif // !defined(INVALID_HANDLE_VALUE)
#endif // defined(_WIN32)
template <class... Ts> struct overloaded : Ts... {
using Ts::operator()...;
};
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template <typename T> struct is_collection {
static const bool value = false;
};
template <typename T, typename A>
struct is_collection<std::vector<T, A>> : std::true_type {};
template <typename T> struct is_collection<std::deque<T>> : std::true_type {};
template <> struct is_collection<std::string> : std::true_type {};
template <> struct is_collection<std::wstring> : std::true_type {};
struct file_deleter final {
void operator()(FILE *file) {
if (file != nullptr) {
fclose(file);
}
}
};
using file_t = std::unique_ptr<FILE, file_deleter>;
struct http_range final {
std::uint64_t begin{};
std::uint64_t end{};
};
using http_headers = std::map<std::string, std::string>;
using http_query_parameters = std::map<std::string, std::string>;
using http_ranges = std::vector<http_range>;
} // namespace monitarr
#endif // defined(__cplusplus)
#define MONITARR_USES_FUNCTION_NAME() \
static constexpr const std::string_view function_name { \
static_cast<const char *>(__FUNCTION__), \
}
#endif // MONITARR_INCLUDE_UTILS_CONFIG_HPP_

View File

@@ -0,0 +1,170 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_COMMON_HPP_
#define MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_COMMON_HPP_
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/config.hpp"
#include "utils/error.hpp"
namespace monitarr::utils::db::sqlite {
using db_types_t = std::variant<std::int64_t, std::string>;
struct sqlite3_deleter final {
void operator()(sqlite3 *db3) const;
};
using db3_t = std::unique_ptr<sqlite3, sqlite3_deleter>;
struct sqlite3_statement_deleter final {
void operator()(sqlite3_stmt *stmt) const {
if (stmt != nullptr) {
sqlite3_finalize(stmt);
}
}
};
using db3_stmt_t = std::unique_ptr<sqlite3_stmt, sqlite3_statement_deleter>;
[[nodiscard]] auto
create_db(std::string db_path,
const std::map<std::string, std::string> &sql_create_tables) -> db3_t;
[[nodiscard]] auto execute_sql(sqlite3 &db3, const std::string &sql,
std::string &err) -> bool;
void set_journal_mode(sqlite3 &db3);
struct db_comp_data_t final {
std::string column_name;
std::string op_type;
};
struct db_context_t {
db_context_t(sqlite3 *db3_, std::string table_name_)
: db3(db3_), table_name(std::move(table_name_)) {}
sqlite3 *db3{};
std::string table_name;
};
struct db_result final {
struct context final {
db3_stmt_t stmt;
};
class db_column final {
public:
db_column(std::int32_t index, std::string name, db_types_t value) noexcept;
db_column() noexcept = default;
db_column(const db_column &) = default;
db_column(db_column &&column) noexcept = default;
~db_column() = default;
auto operator=(const db_column &) -> db_column & = default;
auto operator=(db_column &&) -> db_column & = default;
private:
std::int32_t index_{};
std::string name_;
db_types_t value_;
public:
[[nodiscard]] auto get_index() const -> std::int32_t { return index_; }
[[nodiscard]] auto get_name() const -> std::string { return name_; }
template <typename data_type>
[[nodiscard]] auto get_value() const -> data_type {
MONITARR_USES_FUNCTION_NAME();
return std::visit(
overloaded{
[](const data_type &value) -> data_type { return value; },
[](auto &&) -> data_type {
throw utils::error::create_exception(
function_name, {
"data type not supported",
});
},
},
value_);
}
#if defined(PROJECT_ENABLE_JSON)
[[nodiscard]] auto get_value_as_json() const -> nlohmann::json;
#endif // defined(PROJECT_ENABLE_JSON)
};
class db_row final {
public:
db_row(std::shared_ptr<context> ctx);
private:
std::map<std::string, db_column> columns_;
public:
[[nodiscard]] auto get_columns() const -> std::vector<db_column>;
[[nodiscard]] auto get_column(std::int32_t index) const -> db_column;
[[nodiscard]] auto get_column(std::string name) const -> db_column;
};
db_result(db3_stmt_t stmt, std::int32_t res);
db_result() = default;
db_result(const db_result &) = default;
db_result(db_result &&) noexcept = default;
auto operator=(const db_result &) -> db_result & = default;
auto operator=(db_result &&) -> db_result & = default;
using row = db_row;
private:
std::shared_ptr<context> ctx_;
mutable std::int32_t res_{};
private:
void set_res(std::int32_t res) const { res_ = res; }
public:
[[nodiscard]] auto get_error() const -> std::int32_t { return res_; }
[[nodiscard]] auto get_error_str() const -> std::string;
[[nodiscard]] auto get_row(std::optional<row> &opt_row) const -> bool;
[[nodiscard]] auto has_row() const -> bool;
void next_row() const;
[[nodiscard]] auto ok() const -> bool;
};
} // namespace monitarr::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)
#endif // MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_COMMON_HPP_

View File

@@ -0,0 +1,73 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_DELETE_HPP_
#define MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_DELETE_HPP_
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_common.hpp"
#include "utils/db/sqlite/db_where_t.hpp"
namespace monitarr::utils::db::sqlite {
class db_delete final {
public:
struct context final : db_context_t {
struct db_delete_op_t final {
std::shared_ptr<context> ctx;
[[nodiscard]] auto dump() const -> std::string;
[[nodiscard]] auto go() const -> db_result;
};
context(sqlite3 *db3_, std::string table_name_)
: db_context_t(db3_, table_name_) {}
using w_t = db_where_t<context, db_delete_op_t>;
using wd_t = where_data_t<w_t>;
std::unique_ptr<wd_t> where_data;
};
public:
db_delete(sqlite3 &db3, std::string table_name)
: ctx_(std::make_shared<context>(&db3, table_name)) {}
db_delete(std::shared_ptr<context> ctx) : ctx_(std::move(ctx)) {}
private:
std::shared_ptr<context> ctx_;
public:
[[nodiscard]] auto dump() const -> std::string;
[[nodiscard]] auto go() const -> db_result;
[[nodiscard]] auto
group(context::w_t::group_func_t func) -> context::w_t::wn_t;
[[nodiscard]] auto where(std::string column_name) const -> context::w_t::cn_t;
};
} // namespace monitarr::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)
#endif // MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_DELETE_HPP_

View File

@@ -0,0 +1,64 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_INSERT_HPP_
#define MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_INSERT_HPP_
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_common.hpp"
namespace monitarr::utils::db::sqlite {
class db_insert final {
public:
struct context final : db_context_t {
context(sqlite3 *db3_, std::string table_name_)
: db_context_t(db3_, table_name_) {}
bool or_replace{false};
std::map<std::string, db_types_t> values;
};
public:
db_insert(sqlite3 &db3, std::string table_name)
: ctx_(std::make_shared<context>(&db3, table_name)) {}
db_insert(std::shared_ptr<context> ctx) : ctx_(std::move(ctx)) {}
private:
std::shared_ptr<context> ctx_;
public:
[[nodiscard]] auto or_replace() -> db_insert {
ctx_->or_replace = true;
return *this;
}
[[nodiscard]] auto column_value(std::string column_name, db_types_t value)
-> db_insert;
[[nodiscard]] auto dump() const -> std::string;
[[nodiscard]] auto go() const -> db_result;
};
} // namespace monitarr::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)
#endif // MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_INSERT_HPP_

View File

@@ -0,0 +1,104 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_SELECT_HPP_
#define MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_SELECT_HPP_
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_common.hpp"
#include "utils/db/sqlite/db_where_t.hpp"
namespace monitarr::utils::db::sqlite {
class db_select final {
public:
struct context final : db_context_t {
struct db_select_op_t final {
std::shared_ptr<context> ctx;
[[nodiscard]] auto dump() const -> std::string;
[[nodiscard]] auto go() const -> db_result;
[[nodiscard]] auto group_by(std::string column_name) -> db_select_op_t;
[[nodiscard]] auto limit(std::int32_t value) -> db_select_op_t;
[[nodiscard]] auto offset(std::int32_t value) -> db_select_op_t;
[[nodiscard]] auto order_by(std::string column_name, bool ascending)
-> db_select_op_t;
};
context(sqlite3 *db3_, std::string table_name_)
: db_context_t(db3_, table_name_) {}
using w_t = db_where_t<context, db_select_op_t>;
using wd_t = where_data_t<w_t>;
std::vector<std::string> columns;
std::map<std::string, std::string> count_columns;
std::vector<std::string> group_by;
std::optional<std::int32_t> limit;
std::optional<std::int32_t> offset;
std::optional<std::pair<std::string, bool>> order_by;
std::unique_ptr<wd_t> where_data;
};
public:
db_select(sqlite3 &db3, std::string table_name)
: ctx_(std::make_shared<context>(&db3, table_name)) {}
db_select(std::shared_ptr<context> ctx) : ctx_(std::move(ctx)) {}
private:
std::shared_ptr<context> ctx_;
public:
[[nodiscard]] auto column(std::string column_name) -> db_select;
[[nodiscard]] auto count(std::string column_name, std::string as_column_name)
-> db_select;
[[nodiscard]] auto dump() const -> std::string;
[[nodiscard]] auto go() const -> db_result;
[[nodiscard]] auto group_by(std::string column_name) -> db_select;
[[nodiscard]] auto group(context::w_t::group_func_t func)
-> context::w_t::wn_t;
[[nodiscard]] auto limit(std::int32_t value) -> db_select;
[[nodiscard]] auto offset(std::int32_t value) -> db_select;
[[nodiscard]] auto order_by(std::string column_name, bool ascending)
-> db_select;
[[nodiscard]] auto where(std::string column_name) const -> context::w_t::cn_t;
};
} // namespace monitarr::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)
#endif // MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_SELECT_HPP_

View File

@@ -0,0 +1,91 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_UPDATE_HPP_
#define MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_UPDATE_HPP_
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_common.hpp"
#include "utils/db/sqlite/db_where_t.hpp"
namespace monitarr::utils::db::sqlite {
class db_update final {
public:
struct context final : db_context_t {
context(sqlite3 *db3_, std::string table_name_)
: db_context_t(db3_, table_name_) {}
struct db_update_op_t final {
std::shared_ptr<context> ctx;
[[nodiscard]] auto dump() const -> std::string;
[[nodiscard]] auto go() const -> db_result;
[[nodiscard]] auto limit(std::int32_t value) -> db_update_op_t;
[[nodiscard]] auto order_by(std::string column_name, bool ascending)
-> db_update_op_t;
};
using w_t = db_where_t<context, db_update_op_t>;
using wd_t = where_data_t<w_t>;
std::map<std::string, db_types_t> column_values;
std::optional<std::int32_t> limit;
std::optional<std::pair<std::string, bool>> order_by;
std::unique_ptr<wd_t> where_data;
};
public:
db_update(sqlite3 &db3, std::string table_name)
: ctx_(std::make_shared<context>(&db3, table_name)) {}
db_update(std::shared_ptr<context> ctx) : ctx_(std::move(ctx)) {}
private:
std::shared_ptr<context> ctx_;
public:
[[nodiscard]] auto column_value(std::string column_name, db_types_t value)
-> db_update;
[[nodiscard]] auto dump() const -> std::string;
[[nodiscard]] auto go() const -> db_result;
[[nodiscard]] auto group(context::w_t::group_func_t func)
-> context::w_t::wn_t;
[[nodiscard]] auto limit(std::int32_t value) -> db_update;
[[nodiscard]] auto order_by(std::string column_name, bool ascending)
-> db_update;
[[nodiscard]] auto where(std::string column_name) const -> context::w_t::cn_t;
};
} // namespace monitarr::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)
#endif // MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_UPDATE_HPP_

View File

@@ -0,0 +1,224 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_WHERE_T_HPP_
#define MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_WHERE_T_HPP_
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_common.hpp"
namespace monitarr::utils::db::sqlite {
template <typename w_t> struct where_data_t final {
w_t base;
std::map<std::size_t, std::vector<typename w_t::action_t>> actions;
std::vector<db_types_t> values;
};
template <typename cn_t, typename ctx_t, typename op_t, typename w_t,
typename wn_t>
struct db_next_t final {
std::size_t action_idx{};
std::shared_ptr<ctx_t> ctx;
std::string action;
using group_func_t = std::function<void(w_t &)>;
[[nodiscard]] auto where(std::string column_name) -> cn_t {
return w_t{action_idx, ctx}.where(column_name);
}
[[nodiscard]] auto dump() const -> std::string { return op_t{ctx}.dump(); }
[[nodiscard]] auto dump(std::int32_t &idx) const -> std::string {
return ctx->where_data->base.dump(idx);
}
[[nodiscard]] auto go() const -> auto { return op_t{ctx}.go(); }
[[nodiscard]] auto group(group_func_t func) -> wn_t {
return w_t{action_idx, ctx}.group(std::move(func));
}
[[nodiscard]] auto op() -> op_t {
return op_t{
ctx,
};
}
};
template <typename cn_t, typename ctx_t, typename op_t, typename w_t>
struct db_where_next_t final {
std::size_t action_idx{};
std::shared_ptr<ctx_t> ctx;
using n_t = db_next_t<cn_t, ctx_t, op_t, w_t, db_where_next_t>;
[[nodiscard]] auto and_() -> n_t {
n_t next{
action_idx,
ctx,
"AND",
};
ctx->where_data->actions[action_idx].emplace_back(next);
return next;
}
[[nodiscard]] auto dump() const -> std::string { return op_t{ctx}.dump(); }
[[nodiscard]] auto dump(std::int32_t &idx) const -> std::string {
return ctx->where_data->base.dump(idx);
}
[[nodiscard]] auto go() const -> auto { return op_t{ctx}.go(); }
[[nodiscard]] auto op() -> op_t {
return op_t{
ctx,
};
}
[[nodiscard]] auto or_() -> n_t {
n_t next{
action_idx,
ctx,
"OR",
};
ctx->where_data->actions[action_idx].emplace_back(next);
return next;
}
};
template <typename ctx_t, typename op_t, typename w_t>
struct db_comp_next_t final {
std::size_t action_idx{};
std::shared_ptr<ctx_t> ctx;
std::string column_name;
using wn_t = db_where_next_t<db_comp_next_t, ctx_t, op_t, w_t>;
[[nodiscard]] auto create(std::string operation, db_types_t value) {
ctx->where_data->actions[action_idx].emplace_back(db_comp_data_t{
column_name,
operation,
});
ctx->where_data->values.push_back(value);
return wn_t{
action_idx,
ctx,
};
}
auto equals(db_types_t value) -> wn_t { return create("=", value); };
auto gt(db_types_t value) -> wn_t { return create(">", value); }
auto gte(db_types_t value) -> wn_t { return create(">=", value); }
auto like(db_types_t value) -> wn_t { return create("LIKE", value); }
auto lt(db_types_t value) -> wn_t { return create("<", value); }
auto lte(db_types_t value) -> wn_t { return create("<=", value); }
auto not_equals(db_types_t value) -> wn_t { return create("!=", value); };
};
template <typename ctx_t, typename op_t> struct db_where_t final {
std::size_t action_idx{0U};
std::shared_ptr<ctx_t> ctx;
using cn_t = db_comp_next_t<ctx_t, op_t, db_where_t>;
using wn_t = db_where_next_t<cn_t, ctx_t, op_t, db_where_t>;
using n_t = db_next_t<cn_t, ctx_t, op_t, db_where_t, wn_t>;
using group_func_t = std::function<void(db_where_t &)>;
using action_t = std::variant<db_comp_data_t, n_t, db_where_t>;
[[nodiscard]] static auto dump(std::int32_t &idx,
auto &&actions) -> std::string {
std::stringstream stream;
for (auto &&action : actions) {
std::visit(overloaded{
[&idx, &stream](const db_comp_data_t &comp) {
stream << '"' << comp.column_name << '"' << comp.op_type
<< '?' + std::to_string(++idx);
},
[&idx, &stream](const n_t &next) {
stream << ' ' << next.action << ' ';
},
[&idx, &stream](const db_where_t &where) {
stream << '(' << dump(idx, where.get_actions()) << ')';
},
},
action);
}
return stream.str();
}
[[nodiscard]] auto dump() const -> std::string { return op_t{ctx}.dump(); }
[[nodiscard]] auto dump(std::int32_t &idx) const -> std::string {
return dump(idx, ctx->where_data->actions[action_idx]);
}
[[nodiscard]] auto get_actions() -> auto & {
return ctx->where_data->actions[action_idx];
}
[[nodiscard]] auto get_actions() const -> const auto & {
return ctx->where_data->actions[action_idx];
}
[[nodiscard]] auto group(group_func_t func) -> wn_t {
ctx->where_data->actions[action_idx];
db_where_t where{ctx->where_data->actions.size(), ctx};
func(where);
ctx->where_data->actions[action_idx].emplace_back(where);
return wn_t{
action_idx,
ctx,
};
}
[[nodiscard]] auto where(std::string column_name) -> cn_t {
ctx->where_data->actions[action_idx];
return cn_t{
action_idx,
ctx,
column_name,
};
}
};
} // namespace monitarr::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)
#endif // MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_WHERE_T_HPP_

View File

@@ -0,0 +1,150 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_ENCRYPTING_READER_HPP_
#define MONITARR_INCLUDE_UTILS_ENCRYPTING_READER_HPP_
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#include "utils/config.hpp"
#include "utils/hash.hpp"
#include "utils/types/file/i_file.hpp"
namespace monitarr::utils::encryption {
class encrypting_reader final {
public:
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 = 0U);
encrypting_reader(std::string_view encrypted_file_path,
std::string_view source_path,
stop_type_callback stop_requested_cb,
std::string_view token, std::size_t error_return = 0U);
encrypting_reader(
std::string_view encrypted_file_path, std::string_view source_path,
stop_type_callback stop_requested_cb, std::string_view token,
std::vector<std::array<unsigned char,
crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
iv_list,
std::size_t error_return = 0U);
encrypting_reader(const encrypting_reader &reader);
encrypting_reader(encrypting_reader &&) = delete;
auto operator=(const encrypting_reader &) -> encrypting_reader & = delete;
auto operator=(encrypting_reader &&) -> encrypting_reader & = delete;
~encrypting_reader() noexcept = default;
public:
using iostream = std::basic_iostream<char, std::char_traits<char>>;
using streambuf = std::basic_streambuf<char, std::char_traits<char>>;
private:
utils::encryption::hash_256_t key_;
stop_type_callback stop_requested_cb_;
size_t error_return_;
std::unique_ptr<utils::file::i_file> source_file_;
private:
std::unordered_map<std::size_t, data_buffer> chunk_buffers_;
std::string encrypted_file_name_;
std::string encrypted_file_path_;
std::vector<
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
iv_list_;
std::size_t last_data_chunk_{};
std::size_t last_data_chunk_size_{};
std::uint64_t read_offset_{};
std::uint64_t total_size_{};
private:
static const std::size_t header_size_;
static const std::size_t data_chunk_size_;
static const std::size_t encrypted_chunk_size_;
private:
auto reader_function(char *buffer, size_t size, size_t nitems) -> size_t;
public:
[[nodiscard]] static auto calculate_decrypted_size(std::uint64_t total_size)
-> std::uint64_t;
[[nodiscard]] static auto
calculate_encrypted_size(std::string_view source_path) -> std::uint64_t;
[[nodiscard]] auto create_iostream() const -> std::shared_ptr<iostream>;
[[nodiscard]] static constexpr auto get_encrypted_chunk_size()
-> std::size_t {
return encrypted_chunk_size_;
}
[[nodiscard]] static constexpr auto get_data_chunk_size() -> std::size_t {
return data_chunk_size_;
}
[[nodiscard]] auto get_encrypted_file_name() const -> std::string {
return encrypted_file_name_;
}
[[nodiscard]] auto get_encrypted_file_path() const -> std::string {
return encrypted_file_path_;
}
[[nodiscard]] auto get_error_return() const -> std::size_t {
return error_return_;
}
[[nodiscard]] static constexpr auto get_header_size() -> std::size_t {
return header_size_;
}
[[nodiscard]] auto get_iv_list() -> std::vector<
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>> {
return iv_list_;
}
[[nodiscard]] auto get_stop_requested() const -> bool {
return stop_requested_cb_();
}
[[nodiscard]] auto get_total_size() const -> std::uint64_t {
return total_size_;
}
[[nodiscard]] static auto reader_function(char *buffer, size_t size,
size_t nitems, void *instream)
-> size_t {
return reinterpret_cast<encrypting_reader *>(instream)->reader_function(
buffer, size, nitems);
}
void set_read_position(std::uint64_t position) { read_offset_ = position; }
};
} // namespace monitarr::utils::encryption
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#endif // MONITARR_INCLUDE_UTILS_ENCRYPTING_READER_HPP_

View File

@@ -0,0 +1,224 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_ENCRYPTION_HPP_
#define MONITARR_INCLUDE_UTILS_ENCRYPTION_HPP_
#if defined(PROJECT_ENABLE_LIBSODIUM)
#include "utils/config.hpp"
#include "utils/error.hpp"
#include "utils/hash.hpp"
namespace monitarr::utils::encryption {
inline constexpr const std::uint32_t encryption_header_size{
crypto_aead_xchacha20poly1305_IETF_NPUBBYTES +
crypto_aead_xchacha20poly1305_IETF_ABYTES,
};
template <typename hash_t>
inline auto generate_key(
std::string_view password,
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher =
default_create_hash<hash_t>()) -> hash_t;
template <typename hash_t>
inline auto generate_key(
std::wstring_view password,
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher =
default_create_hash<hash_t>()) -> hash_t;
#if defined(PROJECT_ENABLE_BOOST)
[[nodiscard]] auto decrypt_file_name(std::string_view encryption_token,
std::string &file_name) -> bool;
[[nodiscard]] auto decrypt_file_path(std::string_view encryption_token,
std::string &file_path) -> bool;
template <typename result_t, typename arr_t, std::size_t arr_size>
[[nodiscard]] inline auto decrypt_data(const std::array<arr_t, arr_size> &key,
const unsigned char *buffer,
std::size_t buffer_size,
result_t &res) -> bool {
if (buffer_size > encryption_header_size) {
const std::uint32_t size =
boost::endian::native_to_big(static_cast<std::uint32_t>(buffer_size));
res.resize(buffer_size - encryption_header_size);
return crypto_aead_xchacha20poly1305_ietf_decrypt_detached(
reinterpret_cast<unsigned char *>(res.data()), nullptr,
&buffer[encryption_header_size], res.size(),
&buffer[crypto_aead_xchacha20poly1305_IETF_NPUBBYTES],
reinterpret_cast<const unsigned char *>(&size), sizeof(size),
buffer, key.data()) == 0;
}
return false;
}
template <typename buffer_t, typename result_t, typename arr_t,
std::size_t arr_size>
[[nodiscard]] inline auto decrypt_data(const std::array<arr_t, arr_size> &key,
const buffer_t &buf,
result_t &res) -> bool {
return decrypt_data<result_t>(
key, reinterpret_cast<const unsigned char *>(buf.data()), buf.size(),
res);
}
template <typename buffer_t, typename result_t, typename hash_t = hash_256_t>
[[nodiscard]] inline auto decrypt_data(
std::string_view password, const buffer_t &buf, result_t &res,
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher =
default_create_hash<hash_t>()) -> bool {
return decrypt_data<buffer_t, result_t>(generate_key(password, hasher), buf,
res);
}
template <typename result_t, typename hash_t = hash_256_t>
[[nodiscard]] inline auto decrypt_data(
std::string_view password, const unsigned char *buffer,
std::size_t buffer_size, result_t &res,
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher =
default_create_hash<hash_t>()) -> bool {
return decrypt_data<result_t>(generate_key(password, hasher), buffer,
buffer_size, res);
}
template <typename result_t, typename arr_t, std::size_t arr_size>
inline void
encrypt_data(const std::array<unsigned char,
crypto_aead_xchacha20poly1305_IETF_NPUBBYTES> &iv,
const std::array<arr_t, arr_size> &key,
const unsigned char *buffer, std::size_t buffer_size,
result_t &res) {
MONITARR_USES_FUNCTION_NAME();
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_ABYTES> mac{};
const std::uint32_t size = boost::endian::native_to_big(
static_cast<std::uint32_t>(buffer_size + encryption_header_size));
res.resize(buffer_size + encryption_header_size);
unsigned long long mac_length{};
if (crypto_aead_xchacha20poly1305_ietf_encrypt_detached(
reinterpret_cast<unsigned char *>(&res[encryption_header_size]),
mac.data(), &mac_length, buffer, buffer_size,
reinterpret_cast<const unsigned char *>(&size), sizeof(size), nullptr,
iv.data(), key.data()) != 0) {
throw monitarr::utils::error::create_exception(function_name,
{
"encryption failed",
});
}
std::memcpy(res.data(), iv.data(), iv.size());
std::memcpy(&res[iv.size()], mac.data(), mac.size());
}
template <typename result_t, typename arr_t, std::size_t arr_size>
inline void encrypt_data(const std::array<arr_t, arr_size> &key,
const unsigned char *buffer, std::size_t buffer_size,
result_t &res) {
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES> iv{};
randombytes_buf(iv.data(), iv.size());
encrypt_data<result_t>(iv, key, buffer, buffer_size, res);
}
template <typename result_t, typename hash_t = hash_256_t>
inline void encrypt_data(
std::string_view password, const unsigned char *buffer,
std::size_t buffer_size, result_t &res,
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher =
default_create_hash<hash_t>()) {
encrypt_data<result_t>(generate_key(password, hasher), buffer, buffer_size,
res);
}
template <typename buffer_t, typename result_t, typename hash_t = hash_256_t>
inline void encrypt_data(
std::string_view password, const buffer_t &buf, result_t &res,
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher =
default_create_hash<hash_t>()) {
encrypt_data<result_t>(generate_key(password, hasher),
reinterpret_cast<const unsigned char *>(buf.data()),
buf.size(), res);
}
template <typename buffer_t, typename result_t, typename arr_t,
std::size_t arr_size>
inline void encrypt_data(const std::array<arr_t, arr_size> &key,
const buffer_t &buf, result_t &res) {
encrypt_data<result_t>(key,
reinterpret_cast<const unsigned char *>(buf.data()),
buf.size(), res);
}
template <typename buffer_t, typename result_t, typename arr_t,
std::size_t arr_size>
inline void
encrypt_data(const std::array<unsigned char,
crypto_aead_xchacha20poly1305_IETF_NPUBBYTES> &iv,
const std::array<arr_t, arr_size> &key, const buffer_t &buf,
result_t &res) {
encrypt_data<result_t>(iv, key,
reinterpret_cast<const unsigned char *>(buf.data()),
buf.size(), res);
}
using reader_func_t =
std::function<bool(data_buffer &cypher_text, std::uint64_t start_offset,
std::uint64_t end_offset)>;
[[nodiscard]] auto
read_encrypted_range(const http_range &range,
const utils::encryption::hash_256_t &key,
reader_func_t reader_func, std::uint64_t total_size,
data_buffer &data) -> bool;
[[nodiscard]] auto read_encrypted_range(
const http_range &range, const utils::encryption::hash_256_t &key,
reader_func_t reader_func, std::uint64_t total_size, unsigned char *data,
std::size_t size, std::size_t &bytes_read) -> bool;
#endif // defined(PROJECT_ENABLE_BOOST)
template <typename hash_t>
inline auto generate_key(
std::string_view password,
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher)
-> hash_t {
return hasher(reinterpret_cast<const unsigned char *>(password.data()),
password.size());
}
template <typename hash_t>
inline auto generate_key(
std::wstring_view password,
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher)
-> hash_t {
return hasher(reinterpret_cast<const unsigned char *>(password.data()),
password.size() * sizeof(wchar_t));
}
} // namespace monitarr::utils::encryption
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
#endif // MONITARR_INCLUDE_UTILS_ENCRYPTION_HPP_

View File

@@ -0,0 +1,104 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_ERROR_HPP_
#define MONITARR_INCLUDE_UTILS_ERROR_HPP_
#include "utils/config.hpp"
namespace monitarr::utils::error {
[[nodiscard]] auto
create_error_message(std::vector<std::string_view> items) -> std::string;
[[nodiscard]] auto
create_exception(std::string_view function_name,
std::vector<std::string_view> items) -> std::runtime_error;
struct i_exception_handler {
virtual ~i_exception_handler() {}
i_exception_handler(const i_exception_handler &) noexcept = delete;
i_exception_handler(i_exception_handler &&) noexcept = delete;
auto operator=(const i_exception_handler &) noexcept = delete;
auto operator=(i_exception_handler &&) noexcept = delete;
virtual void handle_error(std::string_view function_name,
std::string_view msg) const = 0;
virtual void handle_exception(std::string_view function_name) const = 0;
virtual void handle_exception(std::string_view function_name,
const std::exception &ex) const = 0;
protected:
i_exception_handler() = default;
};
struct iostream_exception_handler final : i_exception_handler {
void handle_error(std::string_view function_name,
std::string_view msg) const override {
std::cerr << create_error_message({
function_name,
msg,
})
<< std::endl;
}
void handle_exception(std::string_view function_name) const override {
std::cerr << create_error_message({
function_name,
"exception",
"unknown",
})
<< std::endl;
}
void handle_exception(std::string_view function_name,
const std::exception &ex) const override {
std::cerr << create_error_message({
function_name,
"exception",
(ex.what() == nullptr ? "unknown" : ex.what()),
})
<< std::endl;
}
};
inline const iostream_exception_handler default_exception_handler{};
extern std::atomic<const i_exception_handler *> exception_handler;
#if defined(PROJECT_ENABLE_TESTING)
[[nodiscard]] inline auto
get_exception_handler() -> const i_exception_handler * {
return exception_handler;
}
#endif // defined(PROJECT_ENABLE_TESTING)
void handle_error(std::string_view function_name, std::string_view msg);
void handle_exception(std::string_view function_name);
void handle_exception(std::string_view function_name, const std::exception &ex);
void set_exception_handler(const i_exception_handler *handler);
} // namespace monitarr::utils::error
#endif // MONITARR_INCLUDE_UTILS_ERROR_HPP_

View File

@@ -0,0 +1,182 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_FILE_HPP_
#define MONITARR_INCLUDE_UTILS_FILE_HPP_
#include "utils/config.hpp"
#include "utils/path.hpp"
#include "utils/string.hpp"
#include "utils/types/file/i_directory.hpp"
#include "utils/types/file/i_file.hpp"
#include "utils/types/file/i_fs_item.hpp"
namespace monitarr::utils::file {
[[nodiscard]] auto change_to_process_directory() -> bool;
// INFO: has test
[[nodiscard]] auto create_temp_name(std::string_view file_part) -> std::string;
// INFO: has test
[[nodiscard]] auto
create_temp_name(std::wstring_view file_part) -> std::wstring;
// INFO: has test
[[nodiscard]] inline auto
directory_exists_in_path(std::string_view path,
std::string_view sub_directory) -> bool;
// INFO: has test
[[nodiscard]] inline auto
directory_exists_in_path(std::wstring_view path,
std::wstring_view sub_directory) -> bool;
// INFO: has test
[[nodiscard]] inline auto
file_exists_in_path(std::string_view path, std::string_view file_name) -> bool;
// INFO: has test
[[nodiscard]] inline auto
file_exists_in_path(std::wstring_view path,
std::wstring_view file_name) -> bool;
// INFO: has test
[[nodiscard]] auto
get_free_drive_space(std::string_view path) -> std::optional<std::uint64_t>;
// INFO: has test
[[nodiscard]] auto
get_free_drive_space(std::wstring_view path) -> std::optional<std::uint64_t>;
// INFO: has test
[[nodiscard]] auto get_time(std::string_view path,
time_type type) -> std::optional<std::uint64_t>;
// INFO: has test
[[nodiscard]] auto get_time(std::wstring_view path,
time_type type) -> std::optional<std::uint64_t>;
// INFO: has test
[[nodiscard]] auto
get_times(std::string_view path) -> std::optional<file_times>;
// INFO: has test
[[nodiscard]] auto
get_times(std::wstring_view path) -> std::optional<file_times>;
// INFO: has test
[[nodiscard]] auto
get_total_drive_space(std::string_view path) -> std::optional<std::uint64_t>;
// INFO: has test
[[nodiscard]] auto
get_total_drive_space(std::wstring_view path) -> std::optional<std::uint64_t>;
#if defined(PROJECT_ENABLE_LIBDSM)
[[nodiscard]] auto
smb_create_and_validate_relative_path(std::string_view smb_path,
std::string_view rel_path) -> std::string;
// INFO: has test
[[nodiscard]] auto
smb_create_relative_path(std::string_view smb_path) -> std::string;
// INFO: has test
[[nodiscard]] auto
smb_create_search_path(std::string_view smb_path) -> std::string;
// INFO: has test
[[nodiscard]] auto
smb_create_smb_path(std::string_view smb_path,
std::string_view rel_path) -> std::string;
[[nodiscard]] auto
smb_get_parent_path(std::string_view smb_path) -> std::string;
[[nodiscard]] auto smb_get_root_path(std::string_view smb_path) -> std::string;
[[nodiscard]] auto smb_get_unc_path(std::string_view smb_path) -> std::string;
[[nodiscard]] auto smb_get_uri_path(std::string_view smb_path) -> std::string;
[[nodiscard]] auto smb_get_uri_path(std::string_view smb_path,
std::string_view user,
std::string_view password) -> std::string;
// INFO: has test
[[nodiscard]] auto smb_parent_is_same(std::string_view smb_path1,
std::string_view smb_path2) -> bool;
#define SMB_MOD_RW2 \
(SMB_MOD_READ | SMB_MOD_WRITE | SMB_MOD_READ_EXT | SMB_MOD_WRITE_EXT | \
SMB_MOD_READ_ATTR | SMB_MOD_WRITE_ATTR | SMB_MOD_READ_CTL)
#endif // defined(PROJECT_ENABLE_LIBDSM)
#if defined(PROJECT_ENABLE_JSON)
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
// INFO: has test
[[nodiscard]] auto
read_json_file(std::string_view path, nlohmann::json &data,
std::optional<std::string_view> password = std::nullopt) -> bool;
// INFO: has test
[[nodiscard]] auto read_json_file(
std::wstring_view path, nlohmann::json &data,
std::optional<std::wstring_view> password = std::nullopt) -> bool;
// INFO: has test
[[nodiscard]] auto write_json_file(
std::string_view path, const nlohmann::json &data,
std::optional<std::string_view> password = std::nullopt) -> bool;
// INFO: has test
[[nodiscard]] auto write_json_file(
std::wstring_view path, const nlohmann::json &data,
std::optional<std::wstring_view> password = std::nullopt) -> bool;
#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
// INFO: has test
[[nodiscard]] auto read_json_file(std::string_view path,
nlohmann::json &data) -> bool;
// INFO: has test
[[nodiscard]] auto read_json_file(std::wstring_view path,
nlohmann::json &data) -> bool;
// INFO: has test
[[nodiscard]] auto write_json_file(std::string_view path,
const nlohmann::json &data) -> bool;
// INFO: has test
[[nodiscard]] auto write_json_file(std::wstring_view path,
const nlohmann::json &data) -> bool;
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#endif // defined(PROJECT_ENABLE_JSON)
} // namespace monitarr::utils::file
#endif // MONITARR_INCLUDE_UTILS_FILE_HPP_
#include "utils/file_directory.hpp"
#include "utils/file_enc_file.hpp"
#include "utils/file_file.hpp"
#include "utils/file_smb_directory.hpp"
#include "utils/file_smb_file.hpp"
#include "utils/file_thread_file.hpp"

View File

@@ -0,0 +1,124 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_FILE_DIRECTORY_HPP_
#define MONITARR_INCLUDE_UTILS_FILE_DIRECTORY_HPP_
#include "utils/file.hpp"
namespace monitarr::utils::file {
class directory final : public i_directory {
public:
using directory_t = std::unique_ptr<directory>;
directory() noexcept = default;
directory(std::string_view path, stop_type *stop_requested = nullptr)
: path_(utils::path::absolute(path)), stop_requested_(stop_requested) {}
directory(std::wstring_view path, stop_type *stop_requested = nullptr)
: path_(utils::path::absolute(utils::string::to_utf8(path))),
stop_requested_(stop_requested) {}
directory(const directory &) noexcept = delete;
directory(directory &&move_dir) noexcept = default;
~directory() override = default;
private:
std::string path_;
stop_type *stop_requested_{nullptr};
public:
[[nodiscard]] auto copy_to(std::string_view new_path,
bool overwrite) const -> bool override;
[[nodiscard]] auto
count(bool recursive = false) const -> std::uint64_t override;
[[nodiscard]] auto
create_directory(std::string_view path = "") const -> fs_directory_t override;
[[nodiscard]] auto create_file(std::string_view file_name,
bool read_only) const -> fs_file_t override;
[[nodiscard]] auto exists() const -> bool override;
[[nodiscard]] auto
get_directory(std::string_view path) const -> fs_directory_t override;
[[nodiscard]] auto
get_directories() const -> std::vector<fs_directory_t> override;
[[nodiscard]] auto
get_file(std::string_view path) const -> fs_file_t override;
[[nodiscard]] auto get_files() const -> std::vector<fs_file_t> override;
[[nodiscard]] auto get_items() const -> std::vector<fs_item_t> override;
[[nodiscard]] auto get_path() const -> std::string override { return path_; }
[[nodiscard]] auto is_stop_requested() const -> bool override;
[[nodiscard]] auto is_symlink() const -> bool override;
[[nodiscard]] auto move_to(std::string_view new_path) -> bool override;
[[nodiscard]] auto remove() -> bool override;
[[nodiscard]] auto remove_recursively() -> bool override;
[[nodiscard]] auto
size(bool recursive = false) const -> std::uint64_t override;
public:
auto operator=(const directory &) noexcept -> directory & = delete;
auto operator=(directory &&move_dir) noexcept -> directory & = default;
[[nodiscard]] operator bool() const override { return exists(); }
};
// INFO: has test
template <typename string_t>
[[nodiscard]] inline auto directory_exists_in_path_t(
std::basic_string_view<typename string_t::value_type> path,
std::basic_string_view<typename string_t::value_type> sub_directory)
-> bool {
return directory(utils::path::combine(path, {sub_directory})).exists();
}
// INFO: has test
inline auto directory_exists_in_path(std::string_view path,
std::string_view sub_directory) -> bool {
return directory_exists_in_path_t<std::string>(path, sub_directory);
}
// INFO: has test
inline auto directory_exists_in_path(std::wstring_view path,
std::wstring_view sub_directory) -> bool {
return directory_exists_in_path_t<std::wstring>(path, sub_directory);
}
} // namespace monitarr::utils::file
#endif // MONITARR_INCLUDE_UTILS_FILE_DIRECTORY_HPP_

View File

@@ -0,0 +1,124 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_FILE_ENC_FILE_HPP_
#define MONITARR_INCLUDE_UTILS_FILE_ENC_FILE_HPP_
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#include "utils/file.hpp"
namespace monitarr::utils::file {
class enc_file final : public i_file {
public:
[[nodiscard]] static auto attach_file(fs_file_t file) -> fs_file_t;
public:
enc_file() noexcept = default;
protected:
enc_file(fs_file_t file);
public:
enc_file(const enc_file &) = delete;
enc_file(enc_file &&move_file) noexcept : file_(std::move(move_file.file_)) {}
~enc_file() override { close(); }
private:
fs_file_t file_;
std::string encryption_token_;
public:
void close() override;
[[nodiscard]] auto copy_to(std::string_view new_path, bool overwrite) const
-> bool override;
[[nodiscard]] auto exists() const -> bool override { return file_->exists(); }
void flush() const override;
[[nodiscard]] auto get_handle() const -> native_handle override {
return file_->get_handle();
}
[[nodiscard]] auto get_path() const -> std::string override {
return file_->get_path();
}
[[nodiscard]] auto get_read_buffer_size() const -> std::uint32_t override {
return file_->get_read_buffer_size();
}
[[nodiscard]] auto get_time(time_type type) const
-> std::optional<std::uint64_t> override {
return file_->get_time(type);
}
[[nodiscard]] auto is_read_only() const -> bool override {
return file_->is_read_only();
}
[[nodiscard]] auto is_symlink() const -> bool override {
return file_->is_symlink();
}
[[nodiscard]] auto move_to(std::string_view new_path) -> bool override;
[[nodiscard]] auto read(unsigned char *data, std::size_t to_read,
std::uint64_t offset,
std::size_t *total_read = nullptr) -> bool override;
[[nodiscard]] auto remove() -> bool override;
auto set_read_buffer_size(std::uint32_t size) -> std::uint32_t override {
return file_->set_read_buffer_size(size);
}
[[nodiscard]] auto size() const -> std::optional<std::uint64_t> override;
[[nodiscard]] auto truncate(std::size_t size) -> bool override;
[[nodiscard]] auto write(const unsigned char *data, std::size_t to_write,
std::size_t offset,
std::size_t *total_written = nullptr)
-> bool override;
public:
[[nodiscard]] operator bool() const override {
return static_cast<bool>(*file_);
}
auto operator=(const enc_file &) noexcept -> enc_file & = delete;
auto operator=(enc_file &&move_file) noexcept -> enc_file & {
if (&move_file != this) {
file_ = std::move(move_file.file_);
}
return *this;
}
};
} // namespace monitarr::utils::file
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#endif // MONITARR_INCLUDE_UTILS_FILE_ENC_FILE_HPP_

View File

@@ -0,0 +1,176 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_FILE_FILE_HPP_
#define MONITARR_INCLUDE_UTILS_FILE_FILE_HPP_
#include "utils/file.hpp"
namespace monitarr::utils::file {
class file final : public i_file {
public:
// [[nodiscard]] static auto
// attach_file(native_handle handle,
// bool read_only = false) -> fs_file_t;
// INFO: has test
[[nodiscard]] static auto open_file(std::string_view path,
bool read_only = false) -> fs_file_t;
[[nodiscard]] static auto open_file(std::wstring_view path,
bool read_only = false) -> fs_file_t {
return open_file(utils::string::to_utf8(path), read_only);
}
// INFO: has test
[[nodiscard]] static auto
open_or_create_file(std::string_view path,
bool read_only = false) -> fs_file_t;
[[nodiscard]] static auto
open_or_create_file(std::wstring_view path,
bool read_only = false) -> fs_file_t {
return open_or_create_file(utils::string::to_utf8(path), read_only);
}
public:
file() noexcept = default;
file(std::string_view path)
: file_(nullptr), path_(utils::path::absolute(path)) {}
file(std::wstring_view path)
: file_(nullptr),
path_(utils::path::absolute(utils::string::to_utf8(path))) {}
private:
file(file_t file_ptr, std::string_view path, bool read_only)
: file_(std::move(file_ptr)), path_(path), read_only_(read_only) {}
public:
file(const file &) = delete;
file(file &&move_file) noexcept
: file_(std::move(move_file.file_)),
path_(std::move(move_file.path_)),
read_only_(move_file.read_only_) {}
~file() override { close(); }
private:
file_t file_;
std::string path_;
bool read_only_{false};
private:
std::atomic_uint32_t read_buffer_size{65536U};
private:
void open();
public:
void close() override;
[[nodiscard]] auto copy_to(std::string_view new_path,
bool overwrite) const -> bool override;
[[nodiscard]] auto exists() const -> bool override;
void flush() const override;
[[nodiscard]] auto get_handle() const -> native_handle override;
[[nodiscard]] auto get_path() const -> std::string override { return path_; }
[[nodiscard]] auto get_read_buffer_size() const -> std::uint32_t override {
return read_buffer_size;
}
[[nodiscard]] auto is_read_only() const -> bool override {
return read_only_;
}
[[nodiscard]] auto is_symlink() const -> bool override;
[[nodiscard]] auto move_to(std::string_view new_path) -> bool override;
[[nodiscard]] auto read(unsigned char *data, std::size_t to_read,
std::uint64_t offset,
std::size_t *total_read = nullptr) -> bool override;
[[nodiscard]] auto remove() -> bool override;
auto set_read_buffer_size(std::uint32_t size) -> std::uint32_t override {
read_buffer_size = size;
return read_buffer_size;
}
#if defined(PROJECT_ENABLE_LIBSODIUM)
[[nodiscard]] auto sha256() -> std::optional<std::string>;
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
[[nodiscard]] auto size() const -> std::optional<std::uint64_t> override;
[[nodiscard]] auto truncate(std::size_t size) -> bool override;
[[nodiscard]] auto
write(const unsigned char *data, std::size_t to_write, std::size_t offset,
std::size_t *total_written = nullptr) -> bool override;
public:
auto operator=(const file &) noexcept -> file & = delete;
auto operator=(file &&move_file) noexcept -> file & {
if (&move_file != this) {
file_ = std::move(move_file.file_);
path_ = std::move(move_file.path_);
read_only_ = move_file.read_only_;
}
return *this;
}
[[nodiscard]] operator bool() const override { return file_ != nullptr; }
};
// INFO: has test
template <typename string_t>
[[nodiscard]] inline auto file_exists_in_path_t(
std::basic_string_view<typename string_t::value_type> path,
std::basic_string_view<typename string_t::value_type> file_name) -> bool {
return file(utils::path::combine(path, {file_name})).exists();
}
// INFO: has test
inline auto file_exists_in_path(std::string_view path,
std::string_view file_name) -> bool {
return file_exists_in_path_t<std::string>(path, file_name);
}
// INFO: has test
inline auto file_exists_in_path(std::wstring_view path,
std::wstring_view file_name) -> bool {
return file_exists_in_path_t<std::wstring>(path, file_name);
}
} // namespace monitarr::utils::file
#endif // MONITARR_INCLUDE_UTILS_FILE_FILE_HPP_

View File

@@ -0,0 +1,140 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_FILE_SMB_DIRECTORY_HPP_
#define MONITARR_INCLUDE_UTILS_FILE_SMB_DIRECTORY_HPP_
#if defined(PROJECT_ENABLE_LIBDSM)
#include "utils/file.hpp"
namespace monitarr::utils::file {
class smb_directory final : public i_directory {
public:
using smb_directory_t = std::unique_ptr<smb_directory>;
[[nodiscard]] static auto
open(std::string_view host, std::string_view user, std::string_view password,
std::string_view path,
stop_type *stop_requested = nullptr) -> smb_directory_t;
[[nodiscard]] static auto
open(std::wstring_view host, std::wstring_view user,
std::wstring_view password, std::wstring_view path,
stop_type *stop_requested = nullptr) -> smb_directory_t;
public:
smb_directory() noexcept = default;
smb_directory(const smb_directory &) noexcept = delete;
smb_directory(smb_directory &&) noexcept = default;
~smb_directory() override = default;
private:
smb_directory(std::string path, smb_session_t session,
std::string_view share_name, smb_tid tid,
stop_type *stop_requested)
: path_(std::move(path)),
session_(std::move(session)),
share_name_(share_name),
tid_(tid),
stop_requested_(stop_requested) {}
private:
std::string path_{};
smb_session_t session_{};
std::string share_name_{};
smb_tid tid_{};
stop_type *stop_requested_{nullptr};
public:
[[nodiscard]] auto
count(bool recursive = false) const -> std::uint64_t override;
[[nodiscard]] auto copy_to(std::string_view new_path,
bool overwrite) const -> bool override;
[[nodiscard]] auto
create_directory(std::string_view path = "") const -> fs_directory_t override;
[[nodiscard]] auto create_file(std::string_view file_name,
bool read_only) const -> fs_file_t override;
[[nodiscard]] auto exists() const -> bool override;
[[nodiscard]] auto
get_directory(std::string_view path) const -> fs_directory_t override;
[[nodiscard]] auto
get_directories() const -> std::vector<fs_directory_t> override;
[[nodiscard]] auto
get_file(std::string_view path) const -> fs_file_t override;
[[nodiscard]] auto get_files() const -> std::vector<fs_file_t> override;
[[nodiscard]] auto get_items() const -> std::vector<fs_item_t> override;
[[nodiscard]] auto get_path() const -> std::string override { return path_; }
[[nodiscard]] auto
get_time(time_type type) const -> std::optional<std::uint64_t> override;
[[nodiscard]] auto get_unc_path() const -> std::string {
return smb_get_unc_path(path_);
}
[[nodiscard]] auto get_uri_path() const -> std::string {
return smb_get_uri_path(path_);
}
[[nodiscard]] auto
get_uri_path(std::string_view user,
std::string_view password) const -> std::string {
return smb_get_uri_path(path_, user, password);
}
[[nodiscard]] auto is_stop_requested() const -> bool override;
[[nodiscard]] auto is_symlink() const -> bool override;
[[nodiscard]] auto move_to(std::string_view new_path) -> bool override;
[[nodiscard]] auto remove() -> bool override;
[[nodiscard]] auto remove_recursively() -> bool override;
[[nodiscard]] auto
size(bool recursive = false) const -> std::uint64_t override;
public:
auto operator=(const smb_directory &) noexcept -> smb_directory & = delete;
auto
operator=(smb_directory &&move_dir) noexcept -> smb_directory & = default;
[[nodiscard]] operator bool() const override { return session_ != nullptr; }
};
} // namespace monitarr::utils::file
#endif // defined(PROJECT_ENABLE_LIBDSM)
#endif // MONITARR_INCLUDE_UTILS_FILE_SMB_DIRECTORY_HPP_

View File

@@ -0,0 +1,157 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_FILE_SMB_FILE_HPP_
#define MONITARR_INCLUDE_UTILS_FILE_SMB_FILE_HPP_
#if defined(PROJECT_ENABLE_LIBDSM)
#include "utils/file.hpp"
namespace monitarr::utils::file {
class smb_file final : public i_file {
public:
smb_file() = default;
smb_file(std::optional<smb_fd> fd, std::string path, smb_session_t session,
std::string_view share_name, smb_tid tid)
: fd_(std::move(fd)),
path_(std::move(path)),
session_(std::move(session)),
share_name_(share_name),
tid_(tid) {}
smb_file(const smb_file &) = delete;
smb_file(smb_file &&f) noexcept
: fd_(std::move(f.fd_)),
path_(std::move(f.path_)),
read_buffer_size(f.get_read_buffer_size()),
read_only_(f.read_only_),
session_(std::move(f.session_)),
share_name_(std::move(f.share_name_)),
tid_(f.tid_) {}
~smb_file() override { close(); }
private:
std::optional<smb_fd> fd_;
std::string path_;
std::atomic_uint32_t read_buffer_size{65536U};
bool read_only_;
smb_session_t session_;
std::string share_name_;
smb_tid tid_;
public:
void close() override;
[[nodiscard]] auto copy_to(std::string_view new_path,
bool overwrite) const -> bool override;
[[nodiscard]] auto exists() const -> bool override;
void flush() const override;
[[nodiscard]] auto get_handle() const -> native_handle override {
return INVALID_HANDLE_VALUE;
}
[[nodiscard]] auto get_path() const -> std::string override { return path_; }
[[nodiscard]] auto get_read_buffer_size() const -> std::uint32_t override {
return read_buffer_size;
}
[[nodiscard]] static auto
get_time(smb_session *session, smb_tid tid, std::string path,
time_type type) -> std::optional<std::uint64_t>;
[[nodiscard]] auto
get_time(time_type type) const -> std::optional<std::uint64_t> override {
return get_time(session_.get(), tid_, path_, type);
}
[[nodiscard]] auto get_unc_path() const -> std::string {
return smb_get_unc_path(path_);
}
[[nodiscard]] auto get_uri_path() const -> std::string {
return smb_get_uri_path(path_);
}
[[nodiscard]] auto
get_uri_path(std::string_view user,
std::string_view password) const -> std::string {
return smb_get_uri_path(path_, user, password);
}
[[nodiscard]] auto is_read_only() const -> bool override {
return read_only_;
}
[[nodiscard]] auto is_symlink() const -> bool override;
[[nodiscard]] auto move_to(std::string_view new_path) -> bool override;
[[nodiscard]] auto open(bool read_only) -> bool;
[[nodiscard]] auto read(unsigned char *data, std::size_t to_read,
std::uint64_t offset,
std::size_t *total_read = nullptr) -> bool override;
[[nodiscard]] auto remove() -> bool override;
auto set_read_buffer_size(std::uint32_t size) -> std::uint32_t override {
read_buffer_size = size;
return read_buffer_size;
}
[[nodiscard]] auto size() const -> std::optional<std::uint64_t> override;
[[nodiscard]] auto truncate(std::size_t size) -> bool override;
[[nodiscard]] auto
write(const unsigned char *data, std::size_t to_write, std::size_t offset,
std::size_t *total_written = nullptr) -> bool override;
public:
auto operator=(const smb_file &) noexcept -> smb_file & = delete;
auto operator=(smb_file &&move_file) noexcept -> smb_file & {
if (this != &move_file) {
fd_ = std::move(move_file.fd_);
path_ = std::move(move_file.path_);
read_buffer_size = move_file.get_read_buffer_size();
read_only_ = move_file.read_only_;
session_ = std::move(move_file.session_);
share_name_ = std::move(move_file.share_name_);
tid_ = move_file.tid_;
}
return *this;
}
[[nodiscard]] operator bool() const override { return fd_.has_value(); }
};
} // namespace monitarr::utils::file
#endif // defined(PROJECT_ENABLE_LIBDSM)
#endif // MONITARR_INCLUDE_UTILS_FILE_SMB_FILE_HPP_

View File

@@ -0,0 +1,180 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_FILE_THREAD_FILE_HPP_
#define MONITARR_INCLUDE_UTILS_FILE_THREAD_FILE_HPP_
#include "utils/file.hpp"
namespace monitarr::utils::file {
class thread_file final : public i_file {
public:
// [[nodiscard]] static auto
// attach_file(native_handle handle,
// bool read_only = false) -> fs_file_t;
[[nodiscard]] static auto attach_file(fs_file_t file) -> fs_file_t;
[[nodiscard]] static auto open_file(std::string_view path,
bool read_only = false) -> fs_file_t;
[[nodiscard]] static auto open_file(std::wstring_view path,
bool read_only = false) -> fs_file_t {
return open_file(utils::string::to_utf8(path), read_only);
}
[[nodiscard]] static auto
open_or_create_file(std::string_view path,
bool read_only = false) -> fs_file_t;
[[nodiscard]] static auto
open_or_create_file(std::wstring_view path,
bool read_only = false) -> fs_file_t {
return open_or_create_file(utils::string::to_utf8(path), read_only);
}
public:
thread_file() noexcept = default;
thread_file(std::string_view path);
thread_file(std::wstring_view path);
protected:
thread_file(fs_file_t file);
public:
thread_file(const thread_file &) = delete;
thread_file(thread_file &&move_file) noexcept
: file_(std::move(move_file.file_)) {}
~thread_file() override;
private:
using action_t = std::function<bool()>;
struct io_item final {
action_t action;
bool complete{false};
std::unique_ptr<std::mutex> mtx{
std::make_unique<std::mutex>(),
};
mutable std::unique_ptr<std::condition_variable> notify{
std::make_unique<std::condition_variable>(),
};
bool success{false};
void done(bool result);
void wait() const;
};
private:
fs_file_t file_;
private:
mutable std::vector<std::shared_ptr<io_item>> actions_;
mutable std::unique_ptr<std::thread> io_thread_;
mutable std::unique_ptr<std::mutex> mtx_{std::make_unique<std::mutex>()};
mutable std::unique_ptr<std::condition_variable> notify_{
std::make_unique<std::condition_variable>(),
};
stop_type stop_requested_{false};
private:
auto do_io(action_t action) const -> bool;
void thread_func() const;
public:
void close() override;
[[nodiscard]] auto copy_to(std::string_view new_path,
bool overwrite) const -> bool override;
[[nodiscard]] auto exists() const -> bool override { return file_->exists(); }
void flush() const override;
[[nodiscard]] auto get_handle() const -> native_handle override {
return file_->get_handle();
}
[[nodiscard]] auto get_path() const -> std::string override {
return file_->get_path();
}
[[nodiscard]] auto get_read_buffer_size() const -> std::uint32_t override {
return file_->get_read_buffer_size();
}
[[nodiscard]] auto
get_time(time_type type) const -> std::optional<std::uint64_t> override {
return file_->get_time(type);
}
[[nodiscard]] auto is_read_only() const -> bool override {
return file_->is_read_only();
}
[[nodiscard]] auto is_symlink() const -> bool override {
return file_->is_symlink();
}
[[nodiscard]] auto move_to(std::string_view new_path) -> bool override;
[[nodiscard]] auto read(unsigned char *data, std::size_t to_read,
std::uint64_t offset,
std::size_t *total_read = nullptr) -> bool override;
[[nodiscard]] auto remove() -> bool override;
auto set_read_buffer_size(std::uint32_t size) -> std::uint32_t override {
return file_->set_read_buffer_size(size);
}
[[nodiscard]] auto size() const -> std::optional<std::uint64_t> override;
[[nodiscard]] auto truncate(std::size_t size) -> bool override;
[[nodiscard]] auto
write(const unsigned char *data, std::size_t to_write, std::size_t offset,
std::size_t *total_written = nullptr) -> bool override;
public:
[[nodiscard]] operator bool() const override {
return static_cast<bool>(*file_);
}
auto operator=(const thread_file &) noexcept -> thread_file & = delete;
auto operator=(thread_file &&move_file) noexcept -> thread_file & {
if (&move_file != this) {
file_ = std::move(move_file.file_);
}
return *this;
}
};
} // namespace monitarr::utils::file
#endif // MONITARR_INCLUDE_UTILS_FILE_THREAD_FILE_HPP_

View File

@@ -0,0 +1,181 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_HASH_HPP_
#define MONITARR_INCLUDE_UTILS_HASH_HPP_
#if defined(PROJECT_ENABLE_LIBSODIUM)
#include "utils/config.hpp"
#include "utils/error.hpp"
namespace monitarr::utils::encryption {
using hash_256_t = std::array<unsigned char, 32U>;
using hash_384_t = std::array<unsigned char, 48U>;
using hash_512_t = std::array<unsigned char, 64U>;
[[nodiscard]] auto create_hash_blake2b_256(std::string_view data) -> hash_256_t;
[[nodiscard]] auto
create_hash_blake2b_256(std::wstring_view data) -> hash_256_t;
[[nodiscard]] auto
create_hash_blake2b_256(const data_buffer &data) -> hash_256_t;
[[nodiscard]] auto create_hash_blake2b_384(std::string_view data) -> hash_384_t;
[[nodiscard]] auto
create_hash_blake2b_384(std::wstring_view data) -> hash_384_t;
[[nodiscard]] auto
create_hash_blake2b_384(const data_buffer &data) -> hash_384_t;
[[nodiscard]] auto
create_hash_blake2b_512(std::wstring_view data) -> hash_512_t;
[[nodiscard]] auto
create_hash_blake2b_512(const data_buffer &data) -> hash_512_t;
[[nodiscard]] auto create_hash_blake2b_512(std::string_view data) -> hash_512_t;
template <typename hash_t>
[[nodiscard]] auto create_hash_blake2b_t(const unsigned char *data,
std::size_t data_size) -> hash_t;
[[nodiscard]] auto create_hash_sha256(std::string_view data) -> hash_256_t;
[[nodiscard]] auto create_hash_sha256(std::wstring_view data) -> hash_256_t;
[[nodiscard]] auto create_hash_sha256(const data_buffer &data) -> hash_256_t;
[[nodiscard]] auto create_hash_sha256(const unsigned char *data,
std::size_t data_size) -> hash_256_t;
[[nodiscard]] auto create_hash_sha512(std::string_view data) -> hash_512_t;
[[nodiscard]] auto create_hash_sha512(std::wstring_view data) -> hash_512_t;
[[nodiscard]] auto create_hash_sha512(const data_buffer &data) -> hash_512_t;
[[nodiscard]] auto create_hash_sha512(const unsigned char *data,
std::size_t data_size) -> hash_512_t;
template <typename hash_t>
[[nodiscard]] inline auto default_create_hash() -> const
std::function<hash_t(const unsigned char *data, std::size_t size)> &;
template <typename hash_t>
auto create_hash_blake2b_t(const unsigned char *data,
std::size_t data_size) -> hash_t {
MONITARR_USES_FUNCTION_NAME();
hash_t hash{};
crypto_generichash_blake2b_state state{};
auto res = crypto_generichash_blake2b_init(&state, nullptr, 0U, hash.size());
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to initialize blake2b",
std::to_string(hash.size() * 8U),
std::to_string(res),
});
}
res = crypto_generichash_blake2b_update(&state, data, data_size);
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to update blake2b",
std::to_string(hash.size() * 8U),
std::to_string(res),
});
}
res = crypto_generichash_blake2b_final(&state, hash.data(), hash.size());
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to finalize blake2b",
std::to_string(hash.size() * 8U),
std::to_string(res),
});
}
return hash;
}
inline const std::function<hash_256_t(const unsigned char *data,
std::size_t size)>
blake2b_256_hasher =
[](const unsigned char *data, std::size_t data_size) -> hash_256_t {
return create_hash_blake2b_t<hash_256_t>(data, data_size);
};
inline const std::function<hash_384_t(const unsigned char *data,
std::size_t size)>
blake2b_384_hasher =
[](const unsigned char *data, std::size_t data_size) -> hash_384_t {
return create_hash_blake2b_t<hash_384_t>(data, data_size);
};
inline const std::function<hash_512_t(const unsigned char *data,
std::size_t size)>
blake2b_512_hasher =
[](const unsigned char *data, std::size_t data_size) -> hash_512_t {
return create_hash_blake2b_t<hash_512_t>(data, data_size);
};
inline const std::function<hash_256_t(const unsigned char *data,
std::size_t size)>
sha256_hasher =
[](const unsigned char *data, std::size_t data_size) -> hash_256_t {
return create_hash_sha256(data, data_size);
};
inline const std::function<hash_512_t(const unsigned char *data,
std::size_t size)>
sha512_hasher =
[](const unsigned char *data, std::size_t data_size) -> hash_512_t {
return create_hash_sha512(data, data_size);
};
template <>
[[nodiscard]] inline auto default_create_hash<hash_256_t>() -> const
std::function<hash_256_t(const unsigned char *data, std::size_t size)> & {
return blake2b_256_hasher;
}
template <>
[[nodiscard]] inline auto default_create_hash<hash_384_t>() -> const
std::function<hash_384_t(const unsigned char *data, std::size_t size)> & {
return blake2b_384_hasher;
}
template <>
[[nodiscard]] inline auto default_create_hash<hash_512_t>() -> const
std::function<hash_512_t(const unsigned char *data, std::size_t size)> & {
return blake2b_512_hasher;
}
} // namespace monitarr::utils::encryption
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
#endif // MONITARR_INCLUDE_UTILS_HASH_HPP_

View File

@@ -0,0 +1,526 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_PATH_HPP_
#define MONITARR_INCLUDE_UTILS_PATH_HPP_
#include "utils/config.hpp"
#include "utils/string.hpp"
namespace monitarr::utils::path {
inline constexpr const std::string_view backslash{"\\"};
inline constexpr const std::wstring_view backslash_w{L"\\"};
inline constexpr const std::string_view dot{"."};
inline constexpr const std::wstring_view dot_w{L"."};
inline constexpr const std::string_view dot_backslash{".\\"};
inline constexpr const std::wstring_view dot_backslash_w{L".\\"};
inline constexpr const std::string_view dot_slash{"./"};
inline constexpr const std::wstring_view dot_slash_w{L"./"};
inline constexpr const std::string_view long_notation{"\\\\?\\"};
inline constexpr const std::wstring_view long_notation_w{L"\\\\?\\"};
inline constexpr const std::string_view slash{"/"};
inline constexpr const std::wstring_view slash_w{L"/"};
#if defined(_WIN32)
inline constexpr const std::string_view directory_seperator{backslash};
inline constexpr const std::wstring_view directory_seperator_w{backslash_w};
inline constexpr const std::string_view not_directory_seperator{slash};
inline constexpr const std::wstring_view not_directory_seperator_w{slash_w};
inline constexpr const std::string_view unc_notation{"\\\\"};
inline constexpr const std::wstring_view unc_notation_w{L"\\\\"};
#else // !defined(_WIN32)
inline constexpr const std::string_view directory_seperator{slash};
inline constexpr const std::wstring_view directory_seperator_w{slash_w};
inline constexpr const std::string_view not_directory_seperator{backslash};
inline constexpr const std::wstring_view not_directory_seperator_w{backslash_w};
#endif // defined(_WIN32)
template <typename char_t>
[[nodiscard]] inline constexpr auto
get_backslash() -> std::basic_string_view<char_t>;
template <>
[[nodiscard]] inline constexpr auto
get_backslash<char>() -> std::basic_string_view<char> {
return backslash;
}
template <>
[[nodiscard]] inline constexpr auto
get_backslash<wchar_t>() -> std::basic_string_view<wchar_t> {
return backslash_w;
}
template <typename char_t>
[[nodiscard]] inline constexpr auto
get_directory_seperator() -> std::basic_string_view<char_t>;
template <>
[[nodiscard]] inline constexpr auto
get_directory_seperator<char>() -> std::basic_string_view<char> {
return directory_seperator;
}
template <>
[[nodiscard]] inline constexpr auto
get_directory_seperator<wchar_t>() -> std::basic_string_view<wchar_t> {
return directory_seperator_w;
}
template <typename char_t>
[[nodiscard]] inline constexpr auto
get_not_directory_seperator() -> std::basic_string_view<char_t>;
template <>
[[nodiscard]] inline constexpr auto
get_not_directory_seperator<char>() -> std::basic_string_view<char> {
return not_directory_seperator;
}
template <>
[[nodiscard]] inline constexpr auto
get_not_directory_seperator<wchar_t>() -> std::basic_string_view<wchar_t> {
return not_directory_seperator_w;
}
template <typename char_t>
[[nodiscard]] inline constexpr auto get_dot() -> std::basic_string_view<char_t>;
template <>
[[nodiscard]] inline constexpr auto
get_dot<char>() -> std::basic_string_view<char> {
return dot;
}
template <>
[[nodiscard]] inline constexpr auto
get_dot<wchar_t>() -> std::basic_string_view<wchar_t> {
return dot_w;
}
template <typename char_t>
[[nodiscard]] inline constexpr auto
get_dot_backslash() -> std::basic_string_view<char_t>;
template <>
[[nodiscard]] inline constexpr auto
get_dot_backslash<char>() -> std::basic_string_view<char> {
return dot_backslash;
}
template <>
[[nodiscard]] inline constexpr auto
get_dot_backslash<wchar_t>() -> std::basic_string_view<wchar_t> {
return dot_backslash_w;
}
template <typename char_t>
[[nodiscard]] inline constexpr auto
get_dot_slash() -> std::basic_string_view<char_t>;
template <>
[[nodiscard]] inline constexpr auto
get_dot_slash<char>() -> std::basic_string_view<char> {
return dot_slash;
}
template <>
[[nodiscard]] inline constexpr auto
get_dot_slash<wchar_t>() -> std::basic_string_view<wchar_t> {
return dot_slash_w;
}
template <typename char_t>
[[nodiscard]] inline constexpr auto
get_long_notation() -> std::basic_string_view<char_t>;
template <>
[[nodiscard]] inline constexpr auto
get_long_notation<char>() -> std::basic_string_view<char> {
return long_notation;
}
template <>
[[nodiscard]] inline constexpr auto
get_long_notation<wchar_t>() -> std::basic_string_view<wchar_t> {
return long_notation_w;
}
template <typename char_t>
[[nodiscard]] inline constexpr auto
get_slash() -> std::basic_string_view<char_t>;
template <>
[[nodiscard]] inline constexpr auto
get_slash<char>() -> std::basic_string_view<char> {
return slash;
}
template <>
[[nodiscard]] inline constexpr auto
get_slash<wchar_t>() -> std::basic_string_view<wchar_t> {
return slash_w;
}
#if defined(_WIN32)
template <typename char_t>
[[nodiscard]] inline constexpr auto
get_unc_notation() -> std::basic_string_view<char_t>;
template <>
[[nodiscard]] inline constexpr auto
get_unc_notation<char>() -> std::basic_string_view<char> {
return unc_notation;
}
template <>
[[nodiscard]] inline constexpr auto
get_unc_notation<wchar_t>() -> std::basic_string_view<wchar_t> {
return unc_notation_w;
}
#endif // defined(_WIN32)
template <typename string_t>
[[nodiscard]] inline auto get_current_path() -> string_t;
[[nodiscard]] auto absolute(std::string_view path) -> std::string;
[[nodiscard]] auto absolute(std::wstring_view path) -> std::wstring;
[[nodiscard]] inline auto
combine(std::string_view path,
const std::vector<std::string_view> &paths) -> std::string;
[[nodiscard]] inline auto
combine(std::wstring_view path,
const std::vector<std::wstring_view> &paths) -> std::wstring;
[[nodiscard]] auto contains_trash_directory(std::string_view path) -> bool;
[[nodiscard]] auto contains_trash_directory(std::wstring_view path) -> bool;
[[nodiscard]] auto inline create_api_path(std::string_view path) -> std::string;
[[nodiscard]] auto inline create_api_path(std::wstring_view path)
-> std::wstring;
[[nodiscard]] auto exists(std::string_view path) -> bool;
[[nodiscard]] auto exists(std::wstring_view path) -> bool;
[[nodiscard]] inline auto finalize(std::string_view path) -> std::string;
[[nodiscard]] inline auto finalize(std::wstring_view path) -> std::wstring;
[[nodiscard]] auto
find_program_in_path(const std::string &name_without_extension) -> std::string;
[[nodiscard]] auto
find_program_in_path(std::wstring_view name_without_extension) -> std::wstring;
template <typename string_t>
inline auto
format_path(string_t &path,
std::basic_string_view<typename string_t::value_type> sep,
std::basic_string_view<typename string_t::value_type> not_sep)
-> string_t &;
[[nodiscard]] inline auto
get_parent_api_path(std::string_view path) -> std::string;
[[nodiscard]] inline auto
get_parent_api_path(std::wstring_view path) -> std::wstring;
[[nodiscard]] auto get_parent_path(std::string_view path) -> std::string;
[[nodiscard]] auto get_parent_path(std::wstring_view path) -> std::wstring;
[[nodiscard]] inline auto
get_parts(std::string_view path) -> std::vector<std::string>;
[[nodiscard]] inline auto
get_parts_w(std::wstring_view path) -> std::vector<std::wstring>;
[[nodiscard]] auto get_relative_path(std::string_view path,
std::string_view root_path) -> std::string;
[[nodiscard]] auto
get_relative_path(std::wstring_view path,
std::wstring_view root_path) -> std::wstring;
[[nodiscard]] auto make_file_uri(std::string_view path) -> std::string;
[[nodiscard]] auto make_file_uri(std::wstring_view path) -> std::wstring;
[[nodiscard]] auto strip_to_file_name(std::string path) -> std::string;
[[nodiscard]] auto strip_to_file_name(std::wstring path) -> std::wstring;
[[nodiscard]] auto unmake_file_uri(std::string_view uri) -> std::string;
[[nodiscard]] auto unmake_file_uri(std::wstring_view uri) -> std::wstring;
template <typename string_t>
[[nodiscard]] inline auto combine_t(
std::basic_string_view<typename string_t::value_type> path,
const std::vector<std::basic_string_view<typename string_t::value_type>>
&paths) -> string_t {
auto dir_sep_t =
string_t{get_directory_seperator<typename string_t::value_type>()};
return absolute(
std::accumulate(paths.begin(), paths.end(),
std::basic_string<typename string_t::value_type>{path},
[&dir_sep_t](auto next_path, auto &&path_part) {
if (next_path.empty()) {
return string_t{path_part};
}
return next_path + dir_sep_t + string_t{path_part};
}));
}
inline auto combine(std::string_view path,
const std::vector<std::string_view> &paths) -> std::string {
return combine_t<std::string>(path, paths);
}
inline auto
combine(std::wstring_view path,
const std::vector<std::wstring_view> &paths) -> std::wstring {
return combine_t<std::wstring>(path, paths);
}
template <typename string_t>
[[nodiscard]] inline auto create_api_path_t(
std::basic_string_view<typename string_t::value_type> path) -> string_t {
auto backslash_t = get_backslash<typename string_t::value_type>();
auto dot_backslash_t = get_dot_backslash<typename string_t::value_type>();
auto dot_slash_t = get_dot_slash<typename string_t::value_type>();
auto dot_t = get_dot<typename string_t::value_type>();
auto slash_t = get_slash<typename string_t::value_type>();
#if defined(_WIN32)
auto long_notation_t = get_long_notation<typename string_t::value_type>();
if (utils::string::begins_with(path, long_notation_t)) {
path = path.substr(long_notation_t.size());
}
#endif // defined(_WIN32)
if (path.empty() || path == backslash_t || path == dot_t ||
path == dot_slash_t || path == slash_t || path == dot_backslash_t) {
return string_t{slash_t};
}
string_t api_path{path};
#if defined(_WIN32)
if ((api_path.size() >= 2U) && (api_path.at(1U) == ':')) {
api_path = api_path.substr(2U);
}
#endif // defined(_WIN32)
format_path(api_path, slash_t, backslash_t);
while (utils::string::begins_with(api_path, dot_slash_t)) {
api_path = api_path.substr(dot_slash_t.size());
}
if (api_path.at(0U) != slash_t.at(0U)) {
return string_t{slash_t} + api_path;
}
return api_path;
}
inline auto create_api_path(std::string_view path) -> std::string {
return create_api_path_t<std::string>(path);
}
inline auto create_api_path(std::wstring_view path) -> std::wstring {
return create_api_path_t<std::wstring>(path);
}
template <typename string_t>
[[nodiscard]] inline auto finalize_t(
std::basic_string_view<typename string_t::value_type> path) -> string_t {
string_t dir_sep_t{get_directory_seperator<typename string_t::value_type>()};
string_t fmt_path{path};
if (fmt_path.empty()) {
return fmt_path;
}
format_path(fmt_path, dir_sep_t,
get_not_directory_seperator<typename string_t::value_type>());
#if defined(_WIN32)
auto unc_notation_t = get_unc_notation<typename string_t::value_type>();
if (utils::string::begins_with(fmt_path, unc_notation_t)) {
return fmt_path;
}
auto dot_t = get_dot<typename string_t::value_type>();
auto dot_sep_t = string_t{dot_t} + dir_sep_t;
if (fmt_path == dot_t || fmt_path == dot_sep_t) {
return get_current_path<string_t>();
}
if (fmt_path == dir_sep_t) {
#if defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
return get_current_path<string_t>().substr(0U, long_notation.size() + 2U);
#else // !defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
return get_current_path<string_t>().substr(0U, 2U);
#endif // defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
}
if (utils::string::begins_with(fmt_path, dir_sep_t)) {
#if defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
return get_current_path<string_t>().substr(0U, long_notation.size() + 2U) +
fmt_path;
#else // !defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
return get_current_path<string_t>().substr(0U, 2U) + fmt_path;
#endif // defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
}
if (utils::string::begins_with(fmt_path, dot_sep_t)) {
return get_current_path<string_t>() + dir_sep_t + fmt_path.substr(2U);
}
#if defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
return string_t{get_long_notation<typename string_t::value_type>()} +
fmt_path;
#endif // defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
#endif // defined(_WIN32)
return fmt_path;
}
inline auto finalize(std::string_view path) -> std::string {
return finalize_t<std::string>(path);
}
inline auto finalize(std::wstring_view path) -> std::wstring {
return finalize_t<std::wstring>(path);
}
template <typename string_t>
inline auto
format_path(string_t &path,
std::basic_string_view<typename string_t::value_type> sep,
std::basic_string_view<typename string_t::value_type> not_sep)
-> string_t & {
utils::string::replace(path, not_sep, sep);
#if defined(_WIN32)
auto is_unc{false};
auto long_notation_t = get_long_notation<typename string_t::value_type>();
auto unc_notation_t = get_unc_notation<typename string_t::value_type>();
if (utils::string::begins_with(path, long_notation_t)) {
path = path.substr(long_notation_t.size());
} else if (utils::string::begins_with(path, unc_notation_t)) {
path = path.substr(unc_notation_t.size());
utils::string::left_trim(path, sep.at(0U));
is_unc = true;
}
#endif // defined(_WIN32)
string_t double_sep(2U, sep.at(0U));
while (utils::string::contains(path, double_sep)) {
utils::string::replace(path, double_sep, sep);
}
if (path != sep) {
utils::string::right_trim(path, sep.at(0U));
}
#if defined(_WIN32)
if (is_unc) {
path = string_t{unc_notation_t} + path;
} else if ((path.size() >= 2U) && (path.at(1U) == ':')) {
path[0U] = utils::string::to_lower(string_t(1U, path.at(0U))).at(0U);
}
#endif // defined(_WIN32)
return path;
}
template <>
[[nodiscard]] inline auto get_current_path<std::string>() -> std::string {
#if defined(_WIN32)
std::string path;
path.resize(monitarr::max_path_length + 1U);
::GetCurrentDirectoryA(static_cast<DWORD>(path.size()), path.data());
path = path.c_str();
return finalize(path);
#else // !defined(_WIN32)
return finalize(std::filesystem::current_path().string());
#endif // defined(_WIN32)
}
template <>
[[nodiscard]] inline auto get_current_path<std::wstring>() -> std::wstring {
return utils::string::from_utf8(get_current_path<std::string>());
}
template <typename string_t>
[[nodiscard]] inline auto get_parent_api_path_t(
std::basic_string_view<typename string_t::value_type> path) -> string_t {
auto slash_t = get_slash<typename string_t::value_type>();
string_t ret{path};
utils::string::right_trim(ret, slash_t.at(0U));
if (ret == slash_t || ret.empty()) {
return string_t{path};
}
auto sub_path = ret.substr(0, ret.rfind(slash_t) + 1);
if (sub_path == slash_t) {
return string_t{sub_path};
}
return sub_path;
}
inline auto get_parent_api_path(std::string_view path) -> std::string {
return create_api_path(get_parent_api_path_t<std::string>(path));
}
inline auto get_parent_api_path(std::wstring_view path) -> std::wstring {
return create_api_path(get_parent_api_path_t<std::wstring>(path));
}
template <typename string_t>
[[nodiscard]] inline auto
get_parts_t(std::basic_string_view<typename string_t::value_type> path)
-> std::vector<string_t> {
return utils::string::split(
path, get_directory_seperator<typename string_t::value_type>().at(0U),
false);
}
inline auto get_parts(std::string_view path) -> std::vector<std::string> {
return get_parts_t<std::string>(path);
}
inline auto get_parts_w(std::wstring_view path) -> std::vector<std::wstring> {
return get_parts_t<std::wstring>(path);
}
} // namespace monitarr::utils::path
#endif // MONITARR_INCLUDE_UTILS_PATH_HPP_

View File

@@ -0,0 +1,469 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_STRING_HPP_
#define MONITARR_INCLUDE_UTILS_STRING_HPP_
#include "utils/config.hpp"
namespace monitarr::utils::string {
template <typename string_t> struct chain_replace_with_hex;
template <typename string_t>
[[nodiscard]] inline auto
char_to_hex(typename string_t::value_type character) -> string_t;
[[nodiscard]] inline auto begins_with(std::string_view str,
std::string_view val) -> bool;
[[nodiscard]] inline auto begins_with(std::wstring_view str,
std::wstring_view val) -> bool;
template <typename string_t>
[[nodiscard]] inline auto case_insensitive_find_string(string_t in_str,
string_t for_str) ->
typename string_t::const_iterator;
[[nodiscard]] inline auto contains(std::string_view str,
std::string_view search) -> bool;
[[nodiscard]] inline auto contains(std::wstring_view str,
std::wstring_view search) -> bool;
[[nodiscard]] inline auto ends_with(std::string_view str,
std::string_view val) -> bool;
[[nodiscard]] inline auto ends_with(std::wstring_view str,
std::wstring_view val) -> bool;
[[nodiscard]] auto from_bool(bool val) -> std::string;
#if defined(PROJECT_ENABLE_BOOST)
[[nodiscard]] auto
from_dynamic_bitset(const boost::dynamic_bitset<> &bitset) -> std::string;
#endif // defined(PROJECT_ENABLE_BOOST)
[[nodiscard]] auto from_utf8(std::string_view str) -> std::wstring;
[[nodiscard]] inline auto is_numeric(std::string_view str) -> bool;
[[nodiscard]] inline auto is_numeric(std::wstring_view str) -> bool;
template <typename string_t>
[[nodiscard]] inline auto join(const std::vector<string_t> &arr,
typename string_t::value_type delim) -> string_t;
template <typename string_t>
auto left_trim(string_t &str,
typename string_t::value_type trim_ch = ' ') -> string_t &;
template <typename string_t>
inline auto replace(string_t &src, typename string_t::value_type character,
typename string_t::value_type with,
std::size_t start_pos = 0U) -> string_t &;
template <typename string_t>
inline auto replace(string_t &src,
std::basic_string_view<typename string_t::value_type> find,
std::basic_string_view<typename string_t::value_type> with,
std::size_t start_pos = 0U) -> string_t &;
template <typename string_t>
[[nodiscard]] inline auto replace_copy(string_t src,
typename string_t::value_type character,
typename string_t::value_type with,
std::size_t start_pos = 0U) -> string_t;
template <typename string_t>
[[nodiscard]] inline auto
replace_copy(string_t src,
std::basic_string_view<typename string_t::value_type> find,
std::basic_string_view<typename string_t::value_type> with,
std::size_t start_pos = 0U) -> string_t;
template <typename string_t>
[[nodiscard]] inline auto
replace_with_hex(string_t &str, typename string_t::value_type character)
-> chain_replace_with_hex<string_t>;
template <typename string_t>
inline auto
right_trim(string_t &str,
typename string_t::value_type trim_ch = ' ') -> string_t &;
[[nodiscard]] inline auto split(std::string_view str, char delim,
bool should_trim) -> std::vector<std::string>;
[[nodiscard]] inline auto split(std::wstring_view str, wchar_t delim,
bool should_trim) -> std::vector<std::wstring>;
[[nodiscard]] inline auto split(std::string_view str, std::string_view delim,
bool should_trim) -> std::vector<std::string>;
[[nodiscard]] inline auto split(std::wstring_view str, std::wstring_view delim,
bool should_trim) -> std::vector<std::wstring>;
#if defined(PROJECT_ENABLE_SFML)
auto replace_sf(sf::String &src, const sf::String &find, const sf::String &with,
std::size_t start_pos = 0U) -> sf::String &;
[[nodiscard]] auto split_sf(sf::String str, wchar_t delim,
bool should_trim) -> std::vector<sf::String>;
#endif // defined(PROJECT_ENABLE_SFML)
[[nodiscard]] auto to_bool(std::string val) -> bool;
[[nodiscard]] auto to_double(const std::string &str) -> double;
#if defined(PROJECT_ENABLE_BOOST)
[[nodiscard]] auto
to_dynamic_bitset(const std::string &val) -> boost::dynamic_bitset<>;
#endif // defined(PROJECT_ENABLE_BOOST)
template <typename string_t>
[[nodiscard]] inline auto to_lower(string_t str) -> string_t;
[[nodiscard]] auto to_int32(const std::string &val) -> std::int32_t;
[[nodiscard]] auto to_int64(const std::string &val) -> std::int64_t;
[[nodiscard]] auto to_size_t(const std::string &val) -> std::size_t;
[[nodiscard]] auto to_uint8(const std::string &val) -> std::uint8_t;
[[nodiscard]] auto to_uint16(const std::string &val) -> std::uint16_t;
[[nodiscard]] auto to_uint32(const std::string &val) -> std::uint32_t;
[[nodiscard]] auto to_uint64(const std::string &val) -> std::uint64_t;
template <typename string_t>
[[nodiscard]] inline auto to_upper(string_t str) -> string_t;
[[nodiscard]] auto to_utf8(std::string_view str) -> std::string;
[[nodiscard]] auto to_utf8(std::wstring_view str) -> std::string;
template <typename string_t>
[[nodiscard]] inline auto zero_pad(string_t str, std::size_t count) -> string_t;
template <typename string_t> struct chain_replace_with_hex final {
explicit chain_replace_with_hex(string_t &value) : str(value) {}
chain_replace_with_hex(const chain_replace_with_hex &) = delete;
chain_replace_with_hex(chain_replace_with_hex &&) = delete;
~chain_replace_with_hex() = default;
auto operator=(const chain_replace_with_hex &) -> chain_replace_with_hex & =
delete;
auto
operator=(chain_replace_with_hex &&) -> chain_replace_with_hex & = delete;
auto operator()(typename string_t::value_type character)
-> chain_replace_with_hex {
return replace_with_hex<string_t>(str, character);
}
string_t &str;
};
template <typename string_t>
inline auto trim(string_t &str,
typename string_t::value_type trim_ch = ' ') -> string_t &;
template <typename string_t>
[[nodiscard]] inline auto
trim_copy(string_t str,
typename string_t::value_type trim_ch = ' ') -> string_t;
template <typename char_t>
[[nodiscard]] inline auto
begins_with_t(std::basic_string_view<char_t> str,
std::basic_string_view<char_t> val) -> bool {
return (str.find(val) == 0U);
}
inline auto begins_with(std::string_view str, std::string_view val) -> bool {
return begins_with_t<std::string_view::value_type>(str, val);
}
inline auto begins_with(std::wstring_view str, std::wstring_view val) -> bool {
return begins_with_t<std::wstring_view::value_type>(str, val);
}
template <typename string_t>
inline auto case_insensitive_find_string(string_t in_str, string_t for_str) ->
typename string_t::const_iterator {
static const auto compare_chars =
[](typename string_t::value_type char_a,
typename string_t::value_type char_b) -> bool {
return (std::tolower(char_a) == std::tolower(char_b));
};
return (std::search(in_str.begin(), in_str.end(), for_str.begin(),
for_str.end(), compare_chars));
}
template <typename string_t>
inline auto char_to_hex(typename string_t::value_type character) -> string_t {
std::basic_stringstream<typename string_t::value_type> stream;
stream << '%' << std::setfill<typename string_t::value_type>('0')
<< std::setw(sizeof(character)) << std::hex
<< static_cast<std::uint32_t>(character);
return stream.str();
}
template <typename char_t>
[[nodiscard]] inline auto
contains_t(std::basic_string_view<char_t> str,
std::basic_string_view<char_t> search) -> bool {
return (str.find(search) != std::basic_string_view<char_t>::npos);
}
inline auto contains(std::string_view str, std::string_view search) -> bool {
return contains_t<std::string_view::value_type>(str, search);
}
inline auto contains(std::wstring_view str, std::wstring_view search) -> bool {
return contains_t<std::wstring_view::value_type>(str, search);
}
template <typename char_t>
[[nodiscard]] inline auto
ends_with_t(std::basic_string_view<char_t> str,
std::basic_string_view<char_t> val) -> bool {
if (val.size() > str.size()) {
return false;
}
return std::equal(val.rbegin(), val.rend(), str.rbegin());
}
inline auto ends_with(std::string_view str, std::string_view val) -> bool {
return ends_with_t<std::string_view::value_type>(str, val);
}
inline auto ends_with(std::wstring_view str, std::wstring_view val) -> bool {
return ends_with_t<std::wstring_view::value_type>(str, val);
}
template <typename char_t>
[[nodiscard]] inline auto
is_numeric_t(std::basic_string_view<char_t> str) -> bool {
if ((str.length() > 1U) && (str.at(0U) == '+' || str.at(0U) == '-')) {
str = str.substr(1U);
}
if (str.empty()) {
return false;
}
auto has_decimal{false};
return std::find_if(str.begin(), str.end(),
[&has_decimal](auto &&cur_ch) -> bool {
if (has_decimal && cur_ch == '.') {
return true;
}
has_decimal = has_decimal || cur_ch == '.';
if (has_decimal) {
return false;
}
return std::isdigit(cur_ch) == 0;
}) == str.end();
}
inline auto is_numeric(std::string_view str) -> bool {
return is_numeric_t<std::string_view::value_type>(str);
}
inline auto is_numeric(std::wstring_view str) -> bool {
return is_numeric_t<std::wstring_view::value_type>(str);
}
template <typename string_t>
inline auto join(const std::vector<string_t> &arr,
typename string_t::value_type delim) -> string_t {
if (arr.empty()) {
return {};
}
return std::accumulate(
std::next(arr.begin()), arr.end(), arr[0U],
[&delim](auto str, const auto &cur) { return str + delim + cur; });
}
template <typename string_t>
auto left_trim(string_t &str,
typename string_t::value_type trim_ch) -> string_t & {
str.erase(0, str.find_first_not_of(trim_ch));
return str;
}
template <typename string_t>
inline auto replace(string_t &src, typename string_t::value_type character,
typename string_t::value_type with,
std::size_t start_pos) -> string_t & {
if (start_pos < src.size()) {
std::replace(std::next(src.begin(), start_pos), src.end(), character, with);
}
return src;
}
template <typename string_t>
inline auto replace(string_t &src,
std::basic_string_view<typename string_t::value_type> find,
std::basic_string_view<typename string_t::value_type> with,
std::size_t start_pos) -> string_t & {
if (start_pos < src.size()) {
while ((start_pos = src.find(find, start_pos)) != string_t::npos) {
src.replace(start_pos, find.size(), with);
start_pos += with.size();
}
}
return src;
}
template <typename string_t>
inline auto replace_copy(string_t src, typename string_t::value_type character,
typename string_t::value_type with,
std::size_t start_pos) -> string_t {
return replace(src, character, with, start_pos);
}
template <typename string_t>
inline auto
replace_copy(string_t src,
std::basic_string_view<typename string_t::value_type> find,
std::basic_string_view<typename string_t::value_type> with,
std::size_t start_pos) -> string_t {
return replace(src, find, with, start_pos);
}
template <typename string_t>
inline auto replace_with_hex(string_t &str,
typename string_t::value_type character)
-> chain_replace_with_hex<string_t> {
return chain_replace_with_hex(
replace(str, string_t{1U, character}, char_to_hex<string_t>(character)));
}
template <typename string_t>
inline auto right_trim(string_t &str,
typename string_t::value_type trim_ch) -> string_t & {
str.erase(str.find_last_not_of(trim_ch) + 1);
return str;
}
template <typename string_t> inline auto to_lower(string_t str) -> string_t {
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
return str;
}
template <typename string_t> inline auto to_upper(string_t str) -> string_t {
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
return str;
}
template <typename string_t>
inline auto trim(string_t &str,
typename string_t::value_type trim_ch) -> string_t & {
return right_trim(left_trim(str, trim_ch), trim_ch);
}
template <typename string_t>
inline auto trim_copy(string_t str,
typename string_t::value_type trim_ch) -> string_t {
return trim(str, trim_ch);
}
template <typename string_t>
[[nodiscard]] inline auto
split_t(std::basic_string_view<typename string_t::value_type> str,
typename string_t::value_type delim,
bool should_trim) -> std::vector<string_t> {
std::vector<string_t> ret;
std::basic_stringstream<typename string_t::value_type> stream{string_t{str}};
string_t val;
while (std::getline(stream, val, delim)) {
if (should_trim) {
trim(val);
}
ret.push_back(std::move(val));
}
return ret;
}
inline auto split(std::string_view str, char delim,
bool should_trim) -> std::vector<std::string> {
return split_t<std::string>(str, delim, should_trim);
}
inline auto split(std::wstring_view str, wchar_t delim,
bool should_trim) -> std::vector<std::wstring> {
return split_t<std::wstring>(str, delim, should_trim);
}
template <typename string_t>
[[nodiscard]] inline auto
split_t(std::basic_string_view<typename string_t::value_type> str,
std::basic_string_view<typename string_t::value_type> delim,
bool should_trim) -> std::vector<string_t> {
auto result = std::views::split(str, delim);
std::vector<string_t> ret{};
for (auto &&word : result) {
auto val = string_t{word.begin(), word.end()};
if (should_trim) {
trim(val);
}
ret.push_back(std::move(val));
}
return ret;
}
inline auto split(std::string_view str, std::string_view delim,
bool should_trim) -> std::vector<std::string> {
return split_t<std::string>(str, delim, should_trim);
}
inline auto split(std::wstring_view str, std::wstring_view delim,
bool should_trim) -> std::vector<std::wstring> {
return split_t<std::wstring>(str, delim, should_trim);
}
template <typename string_t>
inline auto zero_pad(string_t str, std::size_t count) -> string_t {
str.insert(str.begin(), count - str.length(), '0');
return str;
}
} // namespace monitarr::utils::string
#endif // MONITARR_INCLUDE_UTILS_STRING_HPP_

View File

@@ -0,0 +1,69 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_TIME_HPP_
#define MONITARR_INCLUDE_UTILS_TIME_HPP_
#include "utils/config.hpp"
namespace monitarr::utils::time {
inline constexpr const auto NANOS_PER_SECOND{1000000000ULL};
inline constexpr const auto WIN32_TIME_CONVERSION{116444736000000000ULL};
inline constexpr const auto WIN32_TIME_NANOS_PER_TICK{100ULL};
#if defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
[[nodiscard]] inline auto convert_to_utc(time_t time) -> std::time_t {
auto calendar_time = fmt::gmtime(time);
return std::mktime(&calendar_time);
}
#endif // defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
#if defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
[[nodiscard]] inline auto get_current_time_utc() -> std::time_t {
auto calendar_time = fmt::gmtime(std::time(nullptr));
return std::mktime(&calendar_time);
}
#endif // defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
void get_local_time_now(struct tm &local_time);
[[nodiscard]] auto get_time_now() -> std::uint64_t;
#if defined(_WIN32)
auto strptime(const char *s, const char *f, struct tm *tm) -> const char *;
[[nodiscard]] auto unix_time_to_filetime(std::uint64_t unix_time) -> FILETIME;
[[nodiscard]] auto
windows_file_time_to_unix_time(FILETIME win_time) -> std::uint64_t;
[[nodiscard]] auto
windows_time_t_to_unix_time(__time64_t win_time) -> std::uint64_t;
#endif // defined(_WIN32)
[[nodiscard]] auto
unix_time_to_windows_time(std::uint64_t unix_time) -> std::uint64_t;
[[nodiscard]] auto
windows_time_to_unix_time(std::uint64_t win_time) -> std::uint64_t;
} // namespace monitarr::utils::time
#endif // MONITARR_INCLUDE_UTILS_TIME_HPP_

View File

@@ -0,0 +1,81 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_TYPES_FILE_I_DIRECTORY_HPP_
#define MONITARR_INCLUDE_TYPES_FILE_I_DIRECTORY_HPP_
#include "utils/config.hpp"
#include "utils/types/file/i_file.hpp"
#include "utils/types/file/i_fs_item.hpp"
namespace monitarr::utils::file {
struct i_directory : public i_fs_item {
using fs_directory_t = std::unique_ptr<i_directory>;
using fs_file_t = i_file::fs_file_t;
virtual ~i_directory() = default;
[[nodiscard]] virtual auto
count(bool recursive = false) const -> std::uint64_t = 0;
[[nodiscard]] virtual auto
create_directory(std::string_view path = "") const -> fs_directory_t = 0;
[[nodiscard]] virtual auto create_file(std::string_view file_name,
bool read_only) const -> fs_file_t = 0;
[[nodiscard]] virtual auto
get_directory(std::string_view path) const -> fs_directory_t = 0;
[[nodiscard]] virtual auto
get_directories() const -> std::vector<fs_directory_t> = 0;
[[nodiscard]] virtual auto
get_file(std::string_view path) const -> fs_file_t = 0;
[[nodiscard]] virtual auto get_files() const -> std::vector<fs_file_t> = 0;
[[nodiscard]] virtual auto get_items() const -> std::vector<fs_item_t> = 0;
[[nodiscard]] auto is_directory_item() const -> bool override { return true; }
[[nodiscard]] virtual auto is_stop_requested() const -> bool = 0;
[[nodiscard]] virtual auto remove_recursively() -> bool = 0;
[[nodiscard]] virtual auto
size(bool recursive = false) const -> std::uint64_t = 0;
protected:
i_directory() noexcept = default;
i_directory(const i_directory &) noexcept = default;
i_directory(i_directory &&) noexcept = default;
auto operator=(i_directory &&) noexcept -> i_directory & = default;
auto operator=(const i_directory &) noexcept -> i_directory & = default;
};
} // namespace monitarr::utils::file
#endif // MONITARR_INCLUDE_TYPES_FILE_I_DIRECTORY_HPP_

View File

@@ -0,0 +1,93 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_TYPES_FILE_I_FILE_HPP_
#define MONITARR_INCLUDE_TYPES_FILE_I_FILE_HPP_
#include "utils/config.hpp"
#include "utils/types/file/i_fs_item.hpp"
namespace monitarr::utils::file {
struct i_file : public i_fs_item {
using fs_file_t = std::unique_ptr<i_file>;
virtual ~i_file() = default;
virtual void close() = 0;
virtual void flush() const = 0;
[[nodiscard]] virtual auto get_handle() const -> native_handle = 0;
[[nodiscard]] virtual auto get_read_buffer_size() const -> std::uint32_t = 0;
[[nodiscard]] auto is_directory_item() const -> bool override {
return false;
}
[[nodiscard]] virtual auto is_read_only() const -> bool = 0;
[[nodiscard]] virtual auto read(data_buffer &data, std::uint64_t offset,
std::size_t *total_read = nullptr) -> bool {
return read(data.data(), data.size(), offset, total_read);
}
[[nodiscard]] virtual auto
read(unsigned char *data, std::size_t to_read, std::uint64_t offset,
std::size_t *total_read = nullptr) -> bool = 0;
[[nodiscard]] virtual auto
read_all(data_buffer &data, std::uint64_t offset,
std::size_t *total_read = nullptr) -> bool;
virtual auto set_read_buffer_size(std::uint32_t size) -> std::uint32_t = 0;
[[nodiscard]] virtual auto size() const -> std::optional<std::uint64_t> = 0;
[[nodiscard]] virtual auto truncate() -> bool { return truncate(0U); }
[[nodiscard]] virtual auto truncate(std::size_t size) -> bool = 0;
[[nodiscard]] virtual auto
write(const data_buffer &data, std::uint64_t offset,
std::size_t *total_written = nullptr) -> bool {
return write(data.data(), data.size(), offset, total_written);
}
[[nodiscard]] virtual auto
write(const unsigned char *data, std::size_t to_write, std::size_t offset,
std::size_t *total_written = nullptr) -> bool = 0;
protected:
i_file() noexcept = default;
i_file(const i_file &) noexcept = default;
i_file(i_file &&) noexcept = default;
auto operator=(i_file &&) noexcept -> i_file & = default;
auto operator=(const i_file &) noexcept -> i_file & = default;
};
} // namespace monitarr::utils::file
#endif // MONITARR_INCLUDE_TYPES_FILE_I_FILE_HPP_

View File

@@ -0,0 +1,117 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_TYPES_FILE_I_FS_ITEM_HPP_
#define MONITARR_INCLUDE_TYPES_FILE_I_FS_ITEM_HPP_
#include "utils/config.hpp"
#include "utils/error.hpp"
#include "utils/string.hpp"
namespace monitarr::utils::file {
enum class time_type {
accessed,
created,
modified,
written,
};
struct file_times final {
std::uint64_t accessed{};
std::uint64_t created{};
std::uint64_t modified{};
std::uint64_t written{};
[[nodiscard]] auto get(time_type type) const -> std::uint64_t {
MONITARR_USES_FUNCTION_NAME();
switch (type) {
case time_type::accessed:
return accessed;
case time_type::created:
return created;
case time_type::modified:
return modified;
case time_type::written:
return written;
}
throw utils::error::create_exception(function_name,
{
"type_type not supported",
});
}
};
struct i_fs_item {
using fs_item_t = std::unique_ptr<i_fs_item>;
virtual ~i_fs_item() = default;
[[nodiscard]] virtual auto copy_to(std::string_view to_path,
bool overwrite) const -> bool = 0;
[[nodiscard]] virtual auto copy_to(std::wstring_view new_path,
bool overwrite) -> bool {
return copy_to(utils::string::to_utf8(new_path), overwrite);
}
[[nodiscard]] virtual auto exists() const -> bool = 0;
[[nodiscard]] virtual auto get_path() const -> std::string = 0;
[[nodiscard]] virtual auto
get_time(time_type type) const -> std::optional<std::uint64_t>;
[[nodiscard]] virtual auto is_directory_item() const -> bool = 0;
[[nodiscard]] auto is_file_item() const -> bool {
return not is_directory_item();
}
[[nodiscard]] virtual auto is_symlink() const -> bool = 0;
[[nodiscard]] virtual auto move_to(std::string_view new_path) -> bool = 0;
[[nodiscard]] virtual auto move_to(std::wstring_view new_path) -> bool {
return move_to(utils::string::to_utf8(new_path));
}
[[nodiscard]] virtual auto remove() -> bool = 0;
public:
[[nodiscard]] virtual operator bool() const = 0;
protected:
i_fs_item() noexcept = default;
i_fs_item(const i_fs_item &) noexcept = default;
i_fs_item(i_fs_item &&) noexcept = default;
auto operator=(i_fs_item &&) noexcept -> i_fs_item & = default;
auto operator=(const i_fs_item &) noexcept -> i_fs_item & = default;
};
} // namespace monitarr::utils::file
#endif // MONITARR_INCLUDE_TYPES_FILE_I_FS_ITEM_HPP_

View File

@@ -0,0 +1,63 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_UNIX_HPP_
#define MONITARR_INCLUDE_UTILS_UNIX_HPP_
#if !defined(_WIN32)
#include "utils/common.hpp"
#include "utils/config.hpp"
namespace monitarr::utils {
using passwd_callback_t = std::function<void(struct passwd *pass)>;
#if defined(__APPLE__)
template <typename thread_t>
[[nodiscard]] auto convert_to_uint64(const thread_t *thread_ptr)
-> std::uint64_t;
#else // !defined(__APPLE__)
[[nodiscard]] auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t;
#endif // defined(__APPLE__)
[[nodiscard]] auto get_last_error_code() -> int;
[[nodiscard]] auto get_thread_id() -> std::uint64_t;
[[nodiscard]] auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool;
void set_last_error_code(int error_code);
[[nodiscard]] auto use_getpwuid(uid_t uid, passwd_callback_t callback)
-> utils::result;
// template implementations
#if defined(__APPLE__)
template <typename t>
[[nodiscard]] auto convert_to_uint64(const thread_t *thread_ptr)
-> std::uint64_t {
return static_cast<std::uint64_t>(
reinterpret_cast<std::uintptr_t>(thread_ptr));
}
#endif // defined(__APPLE__)
} // namespace monitarr::utils
#endif // !defined(_WIN32)
#endif // MONITARR_INCLUDE_UTILS_UNIX_HPP_

View File

@@ -0,0 +1,47 @@
/*
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.
*/
#ifndef MONITARR_INCLUDE_UTILS_WINDOWS_HPP_
#define MONITARR_INCLUDE_UTILS_WINDOWS_HPP_
#if defined(_WIN32)
#include "utils/config.hpp"
namespace monitarr::utils {
void create_console();
void free_console();
[[nodiscard]] auto get_local_app_data_directory() -> const std::string &;
[[nodiscard]] auto get_last_error_code() -> DWORD;
[[nodiscard]] auto get_thread_id() -> std::uint64_t;
[[nodiscard]] auto is_process_elevated() -> bool;
[[nodiscard]] auto run_process_elevated(std::vector<const char *> args) -> int;
void set_last_error_code(DWORD errorCode);
} // namespace monitarr::utils
#endif // defined(_WIN32)
#endif // MONITARR_INCLUDE_UTILS_WINDOWS_HPP_

45
support/src/backward.cpp Normal file
View 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)

View File

@@ -0,0 +1,174 @@
/*
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 monitarr::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{};
while (first_port != 0U) {
io_context ctx{};
tcp::acceptor acceptor(ctx);
acceptor.open(tcp::v4(), error_code) ||
acceptor.bind({tcp::v4(), first_port}, error_code);
if (not error_code) {
break;
}
++first_port;
}
if (not error_code) {
available_port = first_port;
}
return not error_code;
}
#endif // defined(PROJECT_ENABLE_BOOST)
auto retry_action(retryable_action_t action, std::size_t retry_count,
std::chrono::milliseconds retry_wait) -> bool {
MONITARR_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 monitarr::utils

View 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 monitarr::utils::db::sqlite {
void sqlite3_deleter::operator()(sqlite3 *db3) const {
MONITARR_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;
})) {
monitarr::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) {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 monitarr::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View 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 monitarr::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 monitarr::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View 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 monitarr::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 monitarr::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View 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 monitarr::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 monitarr::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View 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 monitarr::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 monitarr::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@@ -0,0 +1,451 @@
/*
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/collection.hpp"
#include "utils/common.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 monitarr::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 {
MONITARR_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{};
const 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)
: key_(utils::encryption::generate_key<utils::encryption::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)) {
MONITARR_USES_FUNCTION_NAME();
if (not*source_file_) {
throw utils::error::create_exception(function_name, {
"file open failed",
source_path,
});
}
data_buffer result;
utils::encryption::encrypt_data(
key_, reinterpret_cast<const unsigned char *>(file_name.data()),
file_name.size(), result);
encrypted_file_name_ = utils::collection::to_hex_string(result);
if (relative_parent_path.has_value()) {
for (auto &&part :
utils::string::split(relative_parent_path.value(),
utils::path::directory_seperator, false)) {
utils::encryption::encrypt_data(
key_, reinterpret_cast<const unsigned char *>(part.c_str()),
strnlen(part.c_str(), part.size()), result);
encrypted_file_path_ += '/' + utils::collection::to_hex_string(result);
}
encrypted_file_path_ += '/' + encrypted_file_name_;
}
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();
const 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);
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_;
iv_list_.resize(total_chunks);
for (auto &iv : iv_list_) {
randombytes_buf(iv.data(), iv.size());
}
}
encrypting_reader::encrypting_reader(std::string_view encrypted_file_path,
std::string_view source_path,
stop_type_callback stop_requested_cb,
std::string_view token,
std::size_t error_return)
: key_(utils::encryption::generate_key<utils::encryption::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)) {
MONITARR_USES_FUNCTION_NAME();
if (not*source_file_) {
throw utils::error::create_exception(function_name, {
"file open failed",
source_path,
});
}
encrypted_file_path_ = encrypted_file_path;
encrypted_file_name_ = utils::path::strip_to_file_name(encrypted_file_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();
const 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);
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_;
iv_list_.resize(total_chunks);
for (auto &iv : iv_list_) {
randombytes_buf(iv.data(), iv.size());
}
}
encrypting_reader::encrypting_reader(
std::string_view encrypted_file_path, std::string_view source_path,
stop_type_callback stop_requested_cb, std::string_view token,
std::vector<
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
iv_list,
std::size_t error_return)
: key_(utils::encryption::generate_key<utils::encryption::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)) {
MONITARR_USES_FUNCTION_NAME();
if (not*source_file_) {
throw utils::error::create_exception(function_name, {
"file open failed",
source_path,
});
}
encrypted_file_path_ = encrypted_file_path;
encrypted_file_name_ = utils::path::strip_to_file_name(encrypted_file_path_);
auto opt_size = source_file_->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_file_->get_path(),
});
}
auto file_size{opt_size.value()};
const 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);
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_;
iv_list_ = std::move(iv_list);
}
encrypting_reader::encrypting_reader(const encrypting_reader &reader)
: key_(reader.key_),
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)),
chunk_buffers_(reader.chunk_buffers_),
encrypted_file_name_(reader.encrypted_file_name_),
encrypted_file_path_(reader.encrypted_file_path_),
iv_list_(reader.iv_list_),
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_) {
MONITARR_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)
-> std::uint64_t {
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)
-> std::uint64_t {
MONITARR_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,
});
}
auto file_size{opt_size.value()};
const auto total_chunks = utils::divide_with_ceiling(
file_size, static_cast<std::uint64_t>(data_chunk_size_));
return file_size + (total_chunks * encryption_header_size);
}
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::reader_function(char *buffer, size_t size,
size_t nitems) -> size_t {
MONITARR_USES_FUNCTION_NAME();
const auto read_size = static_cast<std::size_t>(std::min(
static_cast<std::uint64_t>(size * nitems), total_size_ - read_offset_));
auto chunk = read_offset_ / encrypted_chunk_size_;
auto chunk_offset = read_offset_ % encrypted_chunk_size_;
std::size_t total_read{};
auto ret = false;
if (read_offset_ < total_size_) {
try {
ret = true;
auto remain = read_size;
while (not get_stop_requested() && ret && (remain != 0U)) {
if (chunk_buffers_.find(chunk) == chunk_buffers_.end()) {
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{};
if ((ret = source_file_->read(file_data, chunk * data_chunk_size_,
&bytes_read))) {
utils::encryption::encrypt_data(iv_list_.at(chunk), key_, file_data,
chunk_buffer);
}
} else if (chunk) {
chunk_buffers_.erase(chunk - 1u);
}
auto &chunk_buffer = chunk_buffers_[chunk];
const auto to_read = std::min(
static_cast<std::size_t>(chunk_buffer.size() - chunk_offset),
remain);
std::memcpy(buffer + 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 monitarr::utils::encryption
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)

View File

@@ -0,0 +1,162 @@
/*
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/collection.hpp"
#include "utils/encrypting_reader.hpp"
#include "utils/path.hpp"
namespace monitarr::utils::encryption {
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_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 read_encrypted_range(const http_range &range,
const utils::encryption::hash_256_t &key,
reader_func_t reader_func, std::uint64_t total_size,
data_buffer &data) -> bool {
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 cypher;
auto start_offset = chunk * encrypted_chunk_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(cypher, start_offset, end_offset)) {
return false;
}
data_buffer source_buffer;
if (not utils::encryption::decrypt_data(key, cypher, source_buffer)) {
return false;
}
cypher.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::back_inserter(data));
remain -= data_size;
source_offset = 0U;
}
return true;
}
auto read_encrypted_range(const http_range &range,
const utils::encryption::hash_256_t &key,
reader_func_t reader_func, std::uint64_t total_size,
unsigned char *data, std::size_t size,
std::size_t &bytes_read) -> bool {
bytes_read = 0U;
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);
std::span dest_buffer(data, size);
for (std::size_t chunk = start_chunk; chunk <= end_chunk; chunk++) {
data_buffer cypher;
auto start_offset = chunk * encrypted_chunk_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(cypher, start_offset, end_offset)) {
return false;
}
data_buffer source_buffer;
if (not utils::encryption::decrypt_data(key, cypher, source_buffer)) {
return false;
}
cypher.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(dest_buffer.begin(), static_cast<std::int64_t>(bytes_read)));
remain -= data_size;
bytes_read += data_size;
source_offset = 0U;
}
return true;
}
} // namespace monitarr::utils::encryption
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined (PROJECT_ENABLE_BOOST)

View 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/error.hpp"
namespace monitarr::utils::error {
std::atomic<const i_exception_handler *> exception_handler{
&default_exception_handler};
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_exception(std::string_view function_name,
std::vector<std::string_view> items)
-> std::runtime_error {
items.insert(items.begin(), function_name);
return std::runtime_error(create_error_message(items));
}
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);
}
void set_exception_handler(const i_exception_handler *handler) {
exception_handler = handler;
}
} // namespace monitarr::utils::error

713
support/src/utils/file.cpp Normal file
View File

@@ -0,0 +1,713 @@
/*
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 monitarr::utils::file {
auto change_to_process_directory() -> bool {
MONITARR_USES_FUNCTION_NAME();
try {
#if defined(_WIN32)
std::string file_name;
file_name.resize(monitarr::max_path_length + 1U);
::GetModuleFileNameA(nullptr, file_name.data(),
static_cast<DWORD>(file_name.size() - 1U));
auto path = utils::path::strip_to_file_name(file_name.c_str());
::SetCurrentDirectoryA(path.c_str());
#else // !defined(_WIN32)
std::string path;
path.resize(PATH_MAX + 1);
#if defined(__APPLE__)
proc_pidpath(getpid(), path.c_str(), path.size());
#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> {
MONITARR_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 st{};
if (statfs64(std::string{path}.c_str(), &st) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get free disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
return st.f_bfree * static_cast<std::uint64_t>(st.f_bsize);
#endif // defined(__linux__)
#if defined(__APPLE__)
struct statvfs st{};
if (statvfs(path.c_str(), &st) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get free disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
return st.f_bfree * static_cast<std::uint64_t>(st.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> {
MONITARR_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, &times.at(0U), &times.at(1U),
&times.at(2U));
::CloseHandle(file_handle);
if (res) {
ret.accessed =
utils::time::windows_file_time_to_unix_time(times.at(1U));
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 st{};
if (_stat64(std::string{path}.c_str(), &st) != 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(st.st_atime);
ret.created = utils::time::windows_time_t_to_unix_time(st.st_ctime);
ret.modified = utils::time::windows_time_t_to_unix_time(st.st_mtime);
ret.written = utils::time::windows_time_t_to_unix_time(st.st_mtime);
#else // !defined(_WIN32)
struct stat64 st{};
if (stat64(std::string{path}.c_str(), &st) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get file times",
std::to_string(utils::get_last_error_code()),
path,
});
}
ret.accessed = static_cast<std::uint64_t>(st.st_atim.tv_nsec) +
static_cast<std::uint64_t>(st.st_atim.tv_sec) *
utils::time::NANOS_PER_SECOND;
ret.created = static_cast<std::uint64_t>(st.st_ctim.tv_nsec) +
static_cast<std::uint64_t>(st.st_ctim.tv_sec) *
utils::time::NANOS_PER_SECOND;
ret.modified = static_cast<std::uint64_t>(st.st_mtim.tv_nsec) +
static_cast<std::uint64_t>(st.st_mtim.tv_sec) *
utils::time::NANOS_PER_SECOND;
ret.written = static_cast<std::uint64_t>(st.st_mtim.tv_nsec) +
static_cast<std::uint64_t>(st.st_mtim.tv_sec) *
utils::time::NANOS_PER_SECOND;
#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> {
MONITARR_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 st{};
if (statfs64(std::string{path}.c_str(), &st) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get total disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
return st.f_blocks * static_cast<std::uint64_t>(st.f_bsize);
#endif // defined(__linux__)
#if defined(__APPLE__)
struct statvfs st{};
if (statvfs(path.c_str(), &st) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get total disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
return st.f_blocks * static_cast<std::uint64_t>(st.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,
&current_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)
MONITARR_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)
MONITARR_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 const 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 {
MONITARR_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 =
monitarr::utils::string::split(smb_path.substr(2U), '/', false);
auto new_parts = monitarr::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 {
MONITARR_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 {
MONITARR_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 = monitarr::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 {
MONITARR_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 = monitarr::utils::string::split(path, '/', false);
parts.erase(parts.begin(), std::next(parts.begin(), 2U));
auto search_path = monitarr::utils::string::join(parts, '\\');
return search_path.empty() ? "\\*" : "\\" + search_path + "\\*";
}
auto smb_get_parent_path(std::string_view smb_path) -> std::string {
MONITARR_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
smb_path,
});
}
auto parts = monitarr::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 {
MONITARR_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
smb_path,
});
}
auto parts = monitarr::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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 monitarr::utils::file

View File

@@ -0,0 +1,486 @@
/*
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/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(monitarr::utils::file::directory)> directory_action,
std::function<bool(monitarr::utils::file::file)> file_action,
monitarr::stop_type *stop_requested) -> bool {
MONITARR_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 = monitarr::utils::path::combine(path, {"*.*"});
auto find = ::FindFirstFileA(search.c_str(), &fd);
if (find == INVALID_HANDLE_VALUE) {
throw monitarr::utils::error::create_exception(
function_name,
{
"failed to open directory",
std::to_string(monitarr::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(monitarr::utils::file::directory{
monitarr::utils::path::combine(path, {fd.cFileName}),
stop_requested,
});
}
} else {
res = file_action(monitarr::utils::file::file(
monitarr::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 monitarr::utils::error::create_exception(
function_name,
{
"failed to open directory",
std::to_string(monitarr::utils::get_last_error_code()),
path,
});
}
struct dirent *de{nullptr};
while (res && (de = readdir(root)) && !is_stop_requested()) {
if (de->d_type == DT_DIR) {
if ((std::string_view(de->d_name) != ".") &&
(std::string_view(de->d_name) != "..")) {
res = directory_action(monitarr::utils::file::directory(
monitarr::utils::path::combine(path, {de->d_name})));
}
} else {
res = file_action(monitarr::utils::file::file(
monitarr::utils::path::combine(path, {de->d_name})));
}
}
closedir(root);
#endif // defined(_WIN32)
return res;
}
} // namespace
namespace monitarr::utils::file {
auto directory::copy_to(std::string_view new_path,
bool overwrite) const -> bool {
MONITARR_USES_FUNCTION_NAME();
try {
throw utils::error::create_exception(
function_name, {
"failed to copy directory",
"not implemented",
utils::string::from_bool(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 directory::count(bool recursive) const -> std::uint64_t {
MONITARR_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 {
MONITARR_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)
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 st {};
return (stat64(path_.c_str(), &st) == 0 && S_ISDIR(st.st_mode));
#endif // defined(_WIN32)
return false;
}
auto directory::get_directory(std::string_view path) const -> fs_directory_t {
MONITARR_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> {
MONITARR_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 {
MONITARR_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 {
MONITARR_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> {
MONITARR_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> {
MONITARR_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 {
MONITARR_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 {
MONITARR_USES_FUNCTION_NAME();
try {
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 directory::remove() -> bool {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 monitarr::utils::file

View File

@@ -0,0 +1,188 @@
/*
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 monitarr::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 { return 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(
{offset, offset + to_read - 1U},
utils::encryption::generate_key<utils::encryption::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());
}
} // namespace monitarr::utils::file
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)

View File

@@ -0,0 +1,594 @@
/*
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 = monitarr::utils::path::absolute(path);
file_size = 0U;
#if defined(_WIN32)
struct _stat64 st{};
auto res = _stat64(std::string{path}.c_str(), &st);
if (res != 0) {
return false;
}
file_size = static_cast<std::uint64_t>(st.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 = monitarr::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 st{};
return (stat64(abs_path.c_str(), &st) == 0 && not S_ISDIR(st.st_mode));
#endif // defined(_WIN32)
}
} // namespace
namespace monitarr::utils::file {
// auto file::attach_file(native_handle handle,
// bool read_only) -> fs_file_t {
// MONITARR_USES_FUNCTION_NAME();
//
// try {
// std::string path;
//
// #if defined(_WIN32)
// path.resize(monitarr::max_path_length + 1U);
// ::GetFinalPathNameByHandleA(handle, path.data(),
// static_cast<DWORD>(path.size()),
// FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
// #else // !defined(_WIN32)
// path.resize(monitarr::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() {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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> {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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> {
MONITARR_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 monitarr::utils::file

View 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 monitarr::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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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> {
MONITARR_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 {
MONITARR_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> {
MONITARR_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> {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 monitarr::utils::file
#endif // defined(PROJECT_ENABLE_LIBDSM)

View 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 monitarr::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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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> {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 {
MONITARR_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> {
MONITARR_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 {
MONITARR_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 {
MONITARR_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 monitarr::utils::file
#endif // defined(PROJECT_ENABLE_LIBDSM)

View 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 monitarr::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 monitarr::utils::file

189
support/src/utils/hash.cpp Normal file
View 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)
#include "utils/hash.hpp"
#include "utils/error.hpp"
namespace monitarr::utils::encryption {
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 {
MONITARR_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 {
MONITARR_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 monitarr::utils::encryption
#endif // defined(PROJECT_ENABLE_LIBSODIUM)

315
support/src/utils/path.cpp Normal file
View 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 {
MONITARR_USES_FUNCTION_NAME();
#if defined(_WIN32)
if (monitarr::utils::string::contains(path, "~\\")) {
monitarr::utils::string::replace(path, "~\\", "%USERPROFILE%\\");
}
if (monitarr::utils::string::contains(path, "~/")) {
monitarr::utils::string::replace(path, "~/", "%USERPROFILE%\\");
}
if (monitarr::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 (monitarr::utils::string::contains(path, "~\\")) {
monitarr::utils::string::replace(path, "~\\", "~/");
}
if (monitarr::utils::string::contains(path, "~/")) {
std::string home{};
auto res =
monitarr::utils::use_getpwuid(getuid(), [&home](struct passwd *pw) {
home = (pw->pw_dir ? pw->pw_dir : "");
if (home.empty() ||
((home == monitarr::utils::path::slash) && (getuid() != 0))) {
home = monitarr::utils::path::combine("/home", {pw->pw_name});
}
});
if (not res) {
throw monitarr::utils::error::create_exception(function_name,
{
"failed to getpwuid",
res.reason,
});
}
path = monitarr::utils::string::replace(path, "~/", home + "/");
}
#endif // defined (_WIN32)
return monitarr::utils::path::finalize(path);
}
} // namespace
namespace monitarr::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(monitarr::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 const std::array<std::string_view, 4U> extension_list{
".bat",
".cmd",
".exe",
".ps1",
};
static constexpr const auto split_char = ';';
#else // !defined(_WIN32)
static constexpr const std::array<std::string_view, 2U> extension_list{
"",
".sh",
};
static constexpr const 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 const std::array<std::string_view, 23U> escape_characters = {
{
" ", "<", ">", "#", "%", "+", "{", "}", "|", "\\", "^", "~",
"[", "]", "`", ";", "/", "?", ":", "@", "=", "&", "$",
}};
static constexpr const 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 const 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 monitarr::utils::path

View File

@@ -0,0 +1,138 @@
/*
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 monitarr::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 {
return str.empty()
? L""
: std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>()
.from_bytes(std::string{str});
}
#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 {
return str.empty()
? ""
: std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>()
.to_bytes(std::wstring{str});
}
} // namespace monitarr::utils::string

View 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 monitarr::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 monitarr::utils::time

View File

@@ -0,0 +1,83 @@
/*
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"
namespace monitarr::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__)
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 {
std::vector<gid_t> groups{};
auto res = use_getpwuid(uid, [&groups](struct passwd *pass) {
int group_count{};
if (getgrouplist(pass->pw_name, pass->pw_gid, nullptr, &group_count) < 0) {
groups.resize(static_cast<std::size_t>(group_count));
#if defined(__APPLE__)
getgrouplist(pass->pw_name, pass->pw_gid,
reinterpret_cast<int *>(groups.data()), &group_count);
#else // !defined(__APPLE__)
getgrouplist(pass->pw_name, pass->pw_gid, groups.data(), &group_count);
#endif // defined(__APPLE__)
}
});
if (not res) {
throw utils::error::create_exception(res.function_name,
{"use_getpwuid failed", res.reason});
}
return collection::includes(groups, gid);
}
auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result {
MONITARR_USES_FUNCTION_NAME();
static std::mutex mtx{};
mutex_lock lock{mtx};
auto *temp_pw = getpwuid(uid);
if (temp_pw == nullptr) {
return {
std::string{function_name},
false,
"'getpwuid' returned nullptr",
};
}
callback(temp_pw);
return {std::string{function_name}};
}
} // namespace monitarr::utils
#endif // !defined(_WIN32)

View File

@@ -0,0 +1,144 @@
/*
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/string.hpp"
namespace monitarr::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_last_error_code() -> DWORD { return ::GetLastError(); }
auto get_local_app_data_directory() -> const std::string & {
MONITARR_USES_FUNCTION_NAME();
static std::string app_data = ([]() -> std::string {
com_init_wrapper cw;
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 te{};
DWORD sz = sizeof(te);
if (::GetTokenInformation(token, TokenElevation, &te, sizeof(te), &sz)) {
ret = (te.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(monitarr::max_path_length + 1);
if (::GetModuleFileNameA(nullptr, full_path.data(),
monitarr::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); }
} // namespace monitarr::utils
#endif // defined(_WIN32)

View File

@@ -0,0 +1,63 @@
/*
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.
*/
#ifndef MONITARR_TEST_INCLUDE_TEST_HPP_
#define MONITARR_TEST_INCLUDE_TEST_HPP_
#if defined(U)
#undef U
#endif // defined(U)
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::_;
using namespace ::testing;
#define COMMA ,
#include "utils/all.hpp"
namespace monitarr::test {
[[nodiscard]] auto create_random_file(std::size_t size)
-> utils::file::i_file &;
[[nodiscard]] auto
generate_test_file_name(std::string_view file_name_no_extension) -> std::string;
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
template <typename buffer_t, typename result_t>
static void decrypt_and_verify(const buffer_t &buffer, std::string_view token,
result_t &result) {
EXPECT_TRUE(utils::encryption::decrypt_data(token, buffer, result));
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto generate_test_directory() -> utils::file::i_directory &;
[[nodiscard]] auto get_test_config_dir() -> std::string;
[[nodiscard]] auto get_test_input_dir() -> std::string;
[[nodiscard]] auto get_test_output_dir() -> std::string;
} // namespace monitarr::test
#endif // MONITARR_TEST_INCLUDE_TEST_HPP_

148
support/test/src/test.cpp Normal file
View File

@@ -0,0 +1,148 @@
/*
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 "test.hpp"
extern int PROJECT_TEST_RESULT;
namespace {
static std::recursive_mutex file_mtx{};
static std::vector<std::unique_ptr<monitarr::utils::file::i_fs_item>>
generated_files{};
struct file_deleter final {
std::string test_output_dir;
~file_deleter() {
generated_files.clear();
if (PROJECT_TEST_RESULT == 0) {
EXPECT_TRUE(monitarr::utils::file::directory(test_output_dir)
.remove_recursively());
}
}
};
const auto deleter{
std::make_unique<file_deleter>(monitarr::test::get_test_output_dir()),
};
} // namespace
namespace monitarr::test {
auto create_random_file(std::size_t size) -> utils::file::i_file & {
auto path = generate_test_file_name("random");
auto file = utils::file::file::open_or_create_file(path);
EXPECT_TRUE(*file);
if (*file) {
data_buffer buf(size);
#if defined(PROJECT_ENABLE_LIBSODIUM)
randombytes_buf(buf.data(), buf.size());
#else // !defined(PROJECT_ENABLE_LIBSODIUM)
thread_local std::mt19937 gen{
static_cast<std::uint_fast32_t>(std::time(nullptr)) ^
static_cast<std::uint_fast32_t>(std::random_device{}()),
};
std::uniform_int_distribution<std::uint8_t> dis(0U, 255U);
std::generate(buf.begin(), buf.end(), [&]() -> auto { return dis(gen); });
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
std::size_t bytes_written{};
EXPECT_TRUE(file->write(buf, 0U, &bytes_written));
EXPECT_EQ(size, bytes_written);
EXPECT_EQ(size, file->size());
}
recur_mutex_lock lock{file_mtx};
generated_files.emplace_back(std::move(file));
return *dynamic_cast<utils::file::i_file *>(generated_files.back().get());
}
auto generate_test_directory() -> utils::file::i_directory & {
auto path = utils::path::combine(
get_test_output_dir(),
{
std::string{"test_dir"} + std::to_string(generated_files.size()),
});
recur_mutex_lock lock{file_mtx};
generated_files.emplace_back(std::unique_ptr<utils::file::i_fs_item>(
new utils::file::directory{path}));
auto &ret =
*dynamic_cast<utils::file::i_directory *>(generated_files.back().get());
EXPECT_TRUE(ret.create_directory());
return ret;
}
auto generate_test_file_name(std::string_view file_name_no_extension)
-> std::string {
auto path = utils::path::combine(
get_test_output_dir(), {
std::string{file_name_no_extension} +
std::to_string(generated_files.size()),
});
recur_mutex_lock lock{file_mtx};
generated_files.emplace_back(
std::unique_ptr<utils::file::i_file>(new utils::file::file{path}));
return generated_files.back()->get_path();
}
auto get_test_config_dir() -> std::string {
static auto test_path = ([]() -> std::string {
auto dir = utils::get_environment_variable("PROJECT_TEST_CONFIG_DIR");
return utils::path::combine(dir.empty() ? "." : dir, {"test_config"});
})();
return test_path;
}
auto get_test_input_dir() -> std::string {
static auto test_path = ([]() -> std::string {
auto dir = utils::get_environment_variable("PROJECT_TEST_INPUT_DIR");
return utils::path::combine(dir.empty() ? "." : dir, {"test_input"});
})();
return test_path;
}
auto get_test_output_dir() -> std::string {
static auto test_path = ([]() -> std::string {
auto temp = utils::file::create_temp_name("project_test");
#if defined(_WIN32)
auto path = utils::path::combine("%TEMP%", {temp});
#else // !defined(_WIN32)
auto path = utils::path::combine("/tmp", {temp});
#endif // defined(_WIN32)
if (not utils::file::directory(path).exists()) {
EXPECT_TRUE(utils::file::directory{path}.create_directory());
}
return path;
})();
return test_path;
}
} // namespace monitarr::test

View 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.
*/
#include "test.hpp"
namespace monitarr {
TEST(utils_collection, excludes) {
auto data = {"cow", "moose", "dog", "chicken"};
EXPECT_FALSE(utils::collection::excludes(data, "chicken"));
EXPECT_FALSE(utils::collection::excludes(data, "cow"));
EXPECT_FALSE(utils::collection::excludes(data, "dog"));
EXPECT_FALSE(utils::collection::excludes(data, "moose"));
EXPECT_TRUE(utils::collection::excludes(data, "mouse"));
}
TEST(utils_collection, includes) {
auto data = {"cow", "moose", "dog", "chicken"};
EXPECT_FALSE(utils::collection::includes(data, "mice"));
EXPECT_TRUE(utils::collection::includes(data, "chicken"));
EXPECT_TRUE(utils::collection::includes(data, "cow"));
EXPECT_TRUE(utils::collection::includes(data, "dog"));
EXPECT_TRUE(utils::collection::includes(data, "moose"));
}
TEST(utils_collection, from_hex_string) {
{
auto data = "0xABCDEF10";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(4U, val.size());
}
{
auto data = " 0xABCDEF10 ";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(4U, val.size());
}
{
auto data = "ABCDEF10";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(4U, val.size());
}
{
auto data = "ACDEF";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(3U, val.size());
}
{
auto data = " ACDEF ";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(3U, val.size());
}
{
auto data = "";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = L"0xABCDEF10";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(4U, val.size());
}
{
auto data = L" 0xABCDEF10 ";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(4U, val.size());
}
{
auto data = L"ABCDEF10";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(4U, val.size());
}
{
auto data = L"ACDEF";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(3U, val.size());
}
{
auto data = L" ACDEF ";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(3U, val.size());
}
{
auto data = L"";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
}
TEST(utils_collection, from_hex_string_fails) {
{
auto data = "ABCDEF1Z";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = "ABC DEF1";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = "0x";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = " 0x ";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = L"ABCDEF1Z";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = L"ABC DEF1";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = L"0x";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = L" 0x";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
}
TEST(utils_collection, to_hex_string) {
{
std::array<std::int8_t, 2U> col{
static_cast<std::int8_t>(0xFF),
static_cast<std::int8_t>(0xEE),
};
auto str = utils::collection::to_hex_string(col);
EXPECT_STREQ("ffee", str.c_str());
auto w_str = utils::collection::to_hex_wstring(col);
EXPECT_STREQ(L"ffee", w_str.c_str());
}
{
std::array<std::uint8_t, 2U> col{
static_cast<std::uint8_t>(0xFF),
static_cast<std::uint8_t>(0xEE),
};
auto str = utils::collection::to_hex_string(col);
EXPECT_STREQ("ffee", str.c_str());
auto w_str = utils::collection::to_hex_wstring(col);
EXPECT_STREQ(L"ffee", w_str.c_str());
}
}
TEST(utils_collection, remove_element) {
{
std::vector<std::uint8_t> col{
static_cast<std::uint8_t>(0xFF),
static_cast<std::uint8_t>(0xEE),
};
utils::collection::remove_element(col, 0xFF);
EXPECT_EQ(1U, col.size());
EXPECT_EQ(static_cast<std::uint8_t>(0xEE), col.at(0U));
}
{
std::vector<std::uint8_t> col{
static_cast<std::uint8_t>(0xFF),
static_cast<std::uint8_t>(0xEE),
};
utils::collection::remove_element(col, 0xEE);
EXPECT_EQ(1U, col.size());
EXPECT_EQ(static_cast<std::uint8_t>(0xFF), col.at(0U));
}
{
std::vector<std::uint8_t> col{
static_cast<std::uint8_t>(0xFF),
static_cast<std::uint8_t>(0xEE),
};
utils::collection::remove_element(col, 0xEF);
EXPECT_EQ(2U, col.size());
EXPECT_EQ(static_cast<std::uint8_t>(0xFF), col.at(0U));
EXPECT_EQ(static_cast<std::uint8_t>(0xEE), col.at(1U));
}
}
} // namespace monitarr

View File

@@ -0,0 +1,306 @@
/*
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 "test.hpp"
namespace monitarr {
TEST(utils_common, calculate_read_size) {
auto read_size = utils::calculate_read_size(0U, 0U, 0U);
EXPECT_EQ(0U, read_size);
read_size = utils::calculate_read_size(5U, 0U, 0U);
EXPECT_EQ(0U, read_size);
read_size = utils::calculate_read_size(0U, 6U, 7U);
EXPECT_EQ(0U, read_size);
read_size = utils::calculate_read_size(7U, 1U, 7U);
EXPECT_EQ(0U, read_size);
read_size = utils::calculate_read_size(5U, 5U, 0U);
EXPECT_EQ(5U, read_size);
read_size = utils::calculate_read_size(5U, 5U, 1U);
EXPECT_EQ(4U, read_size);
}
TEST(utils_common, version_equal) {
EXPECT_EQ(0, utils::compare_version_strings("", ""));
EXPECT_EQ(0, utils::compare_version_strings("1.0", "1.0"));
EXPECT_EQ(0, utils::compare_version_strings("1.0.0", "1.0"));
EXPECT_EQ(0, utils::compare_version_strings("1.0.0.0", "1.0"));
EXPECT_EQ(0, utils::compare_version_strings("1.0.0.0", "1.0.0"));
EXPECT_EQ(0, utils::compare_version_strings(L"", L""));
EXPECT_EQ(0, utils::compare_version_strings(L"1.0", L"1.0"));
EXPECT_EQ(0, utils::compare_version_strings(L"1.0.0", L"1.0"));
EXPECT_EQ(0, utils::compare_version_strings(L"1.0.0.0", L"1.0"));
EXPECT_EQ(0, utils::compare_version_strings(L"1.0.0.0", L"1.0.0"));
}
TEST(utils_common, version_greater) {
EXPECT_EQ(1, utils::compare_version_strings("1.0.1", ""));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "1.0"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "1.0.0"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "1.0.0.0"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1.0", "1.0"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1.0", "1.0.0"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1.0", "1.0.0.0"));
EXPECT_EQ(1, utils::compare_version_strings("1.0", "0.9.9"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "0.9.9"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1.0", "0.9.9"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1", ""));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1", L"1.0"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1", L"1.0.0"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1", L"1.0.0.0"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1.0", L"1.0"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1.0", L"1.0.0"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1.0", L"1.0.0.0"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0", L"0.9.9"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1", L"0.9.9"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1.0", L"0.9.9"));
}
TEST(utils_common, version_less) {
EXPECT_EQ(-1, utils::compare_version_strings("", "1.0"));
EXPECT_EQ(-1, utils::compare_version_strings("0.9.9", "1.0"));
EXPECT_EQ(-1, utils::compare_version_strings("0.9.9", "1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings("0.9.9", "1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings("1.0", "1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings("1.0", "1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings("1.0.0", "1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings("1.0.0", "1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings("1.0.0.0", "1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings("1.0.0.0", "1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings(L"", L"1.0"));
EXPECT_EQ(-1, utils::compare_version_strings(L"0.9.9", L"1.0"));
EXPECT_EQ(-1, utils::compare_version_strings(L"0.9.9", L"1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings(L"0.9.9", L"1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings(L"1.0", L"1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings(L"1.0", L"1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings(L"1.0.0", L"1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings(L"1.0.0", L"1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings(L"1.0.0.0", L"1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings(L"1.0.0.0", L"1.0.1.0"));
}
#if defined(PROJECT_ENABLE_STDUUID)
TEST(utils_common, create_uuid_string) {
{
const auto uuid1 = utils::create_uuid_string();
const auto uuid2 = utils::create_uuid_string();
ASSERT_EQ(36U, uuid1.size());
ASSERT_EQ(36U, uuid2.size());
ASSERT_STRNE(uuid1.c_str(), uuid2.c_str());
}
{
const auto uuid1 = utils::create_uuid_wstring();
const auto uuid2 = utils::create_uuid_wstring();
ASSERT_EQ(36U, uuid1.size());
ASSERT_EQ(36U, uuid2.size());
ASSERT_STRNE(uuid1.c_str(), uuid2.c_str());
}
}
#endif // defined(PROJECT_ENABLE_STDUUID)
#if defined(PROJECT_ENABLE_LIBSODIUM)
TEST(utils_common, generate_secure_random) {
{
auto r1 = utils::generate_secure_random<std::size_t>();
auto r2 = utils::generate_secure_random<std::size_t>();
EXPECT_NE(r1, r2);
}
{
auto r1 = utils::generate_secure_random<std::vector<std::uint8_t>>(6U);
auto r2 = utils::generate_secure_random<std::vector<std::uint8_t>>(6U);
EXPECT_EQ(6U, r1.size());
EXPECT_EQ(r1.size(), r2.size());
EXPECT_NE(r1, r2);
}
{
auto r1 = utils::generate_secure_random<std::array<std::uint8_t, 4U>>();
auto r2 = utils::generate_secure_random<std::array<std::uint8_t, 4U>>();
EXPECT_EQ(4U, r1.size());
EXPECT_EQ(r1.size(), r2.size());
EXPECT_NE(0, std::memcmp(r1.data(), r2.data(), r1.size()));
}
{
auto r1 = utils::generate_secure_random<std::string>(6U);
auto r2 = utils::generate_secure_random<std::string>(6U);
EXPECT_EQ(6U, r1.size());
EXPECT_EQ(r1.size(), r2.size());
EXPECT_NE(0, std::memcmp(r1.data(), r2.data(), r1.size()));
}
{
auto r1 = utils::generate_secure_random<std::wstring>(6U);
auto r2 = utils::generate_secure_random<std::wstring>(6U);
EXPECT_EQ(6U, r1.size());
EXPECT_EQ(r1.size(), r2.size());
EXPECT_NE(0, std::memcmp(r1.data(), r2.data(), r1.size()));
}
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
TEST(utils_common, divide_with_ceiling) {
auto r = utils::divide_with_ceiling(12, 5);
EXPECT_EQ(3, r);
r = utils::divide_with_ceiling(12, 4);
EXPECT_EQ(3, r);
r = utils::divide_with_ceiling(1, 2);
EXPECT_EQ(1, r);
r = utils::divide_with_ceiling(2, 2);
EXPECT_EQ(1, r);
r = utils::divide_with_ceiling(0, 2);
EXPECT_EQ(0, r);
}
TEST(utils_common, generate_random_between_for_signed_integers) {
static constexpr const auto max_iterations{1000000UL};
for (std::size_t idx = 0U; idx < max_iterations; ++idx) {
auto res = utils::generate_random_between(5, 12);
EXPECT_GE(res, 5);
EXPECT_LE(res, 12);
}
for (std::size_t idx = 0U; idx < max_iterations; ++idx) {
auto res = utils::generate_random_between(-5, 12);
EXPECT_GE(res, -5);
EXPECT_LE(res, 12);
}
for (std::size_t idx = 0U; idx < max_iterations; ++idx) {
auto res = utils::generate_random_between(-5, -1);
EXPECT_GE(res, -5);
EXPECT_LE(res, -1);
}
}
TEST(utils_common, generate_random_between_for_unsigned_integers) {
static constexpr const auto max_iterations{1000000UL};
for (std::size_t idx = 0U; idx < max_iterations; ++idx) {
auto res = utils::generate_random_between(5U, 12U);
EXPECT_GE(res, 5);
EXPECT_LE(res, 12);
}
}
TEST(utils_common, generate_random_between_throws_error_on_invalid_range) {
EXPECT_THROW(
{
try {
[[maybe_unused]] auto res = utils::generate_random_between(12, 5);
} catch (const std::range_error &e) {
EXPECT_STREQ("end must be greater than begin", e.what());
throw;
}
},
std::range_error);
EXPECT_THROW(
{
try {
[[maybe_unused]] auto res = utils::generate_random_between(12, 12);
} catch (const std::range_error &e) {
EXPECT_STREQ("end must be greater than begin", e.what());
throw;
}
},
std::range_error);
}
TEST(utils_common, generate_random_string) {
static constexpr const auto max_iterations{10000L};
const auto test_string = [](auto str) {
static std::vector<decltype(str)> list{};
EXPECT_FALSE(utils::collection::includes(list, str));
list.push_back(str);
EXPECT_EQ(16U, str.size());
for (auto &&ch : str) {
auto ch_int = static_cast<std::uint32_t>(ch);
EXPECT_GE(ch_int, 48U);
EXPECT_LE(ch_int, 73U + 48U);
}
};
for (std::size_t idx = 0U; idx < max_iterations; ++idx) {
test_string(utils::generate_random_string(16U));
test_string(utils::generate_random_wstring(16U));
}
}
TEST(utils_common, generate_random_string_for_zero_length) {
EXPECT_TRUE(utils::generate_random_string(0U).empty());
EXPECT_TRUE(utils::generate_random_wstring(0U).empty());
}
TEST(utils_common, get_environment_variable) {
static constexpr const std::string path_env{"PATH"};
std::string path;
#if defined(_WIN32)
path.resize(monitarr::max_path_length + 1U);
auto size = ::GetEnvironmentVariableA(path_env.c_str(), path.data(), 0U);
path.resize(size);
::GetEnvironmentVariableA(path_env.c_str(), path.data(),
static_cast<DWORD>(path.size()));
#else // !defined(_WIN32)
path = std::getenv(path_env.c_str());
#endif // defined(_WIN32)
EXPECT_STREQ(path.c_str(), utils::get_environment_variable(path_env).c_str());
EXPECT_STREQ(
utils::string::from_utf8(path).c_str(),
utils::get_environment_variable(utils::string::from_utf8(path_env))
.c_str());
}
#if defined(PROJECT_ENABLE_BOOST)
TEST(utils_common, get_next_available_port) {
std::uint16_t available_port{};
for (std::uint16_t port = 1025U; port < 1030U; ++port) {
EXPECT_TRUE(utils::get_next_available_port(port, available_port));
EXPECT_GE(available_port, port);
}
}
TEST(utils_common, get_next_available_port_fails_if_starting_point_is_zero) {
std::uint16_t available_port{};
EXPECT_FALSE(utils::get_next_available_port(0U, available_port));
EXPECT_EQ(0U, available_port);
}
#endif // defined(PROJECT_ENABLE_BOOST)
} // namespace monitarr

View File

@@ -0,0 +1,300 @@
/*
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 "test.hpp"
#if defined(PROJECT_ENABLE_SQLITE)
namespace monitarr {
class utils_db_sqlite : public ::testing::Test {
public:
utils::db::sqlite::db3_t db3;
void SetUp() override {
{
sqlite3 *db3_ptr{nullptr};
auto res = sqlite3_open(":memory:", &db3_ptr);
ASSERT_EQ(SQLITE_OK, res);
ASSERT_TRUE(db3_ptr != nullptr);
db3 = utils::db::sqlite::db3_t{
db3_ptr,
utils::db::sqlite::sqlite3_deleter(),
};
}
utils::db::sqlite::db3_stmt_t db3_stmt;
{
std::string sql{
"CREATE TABLE [table] (column1 TEXT PRIMARY KEY UNIQUE "
"NOT NULL, column2 TEXT NOT NULL);",
};
sqlite3_stmt *stmt_ptr{nullptr};
auto res =
sqlite3_prepare_v2(db3.get(), sql.c_str(), -1, &stmt_ptr, nullptr);
db3_stmt = utils::db::sqlite::db3_stmt_t{
stmt_ptr,
utils::db::sqlite::sqlite3_statement_deleter(),
};
ASSERT_EQ(SQLITE_OK, res);
}
auto res = sqlite3_step(db3_stmt.get());
ASSERT_EQ(SQLITE_DONE, res);
}
void TearDown() override { db3.reset(); }
};
static void common_insert(sqlite3 &db3, bool dump = false) {
auto query = utils::db::sqlite::db_insert{db3, "table"}
.column_value("column1", "test0")
.column_value("column2", "test1");
if (dump) {
std::cout << query.dump() << std::endl;
}
auto res = query.go();
EXPECT_TRUE(res.ok());
}
static void common_select(sqlite3 &db3, std::string value1, std::string value2,
bool dump = false) {
auto query = utils::db::sqlite::db_select{db3, "table"};
if (dump) {
std::cout << query.dump() << std::endl;
}
auto res = query.go();
EXPECT_TRUE(res.ok());
EXPECT_TRUE(res.has_row());
std::size_t row_count{};
while (res.has_row()) {
std::optional<utils::db::sqlite::db_result::row> row;
EXPECT_TRUE(res.get_row(row));
EXPECT_TRUE(row.has_value());
if (row.has_value()) {
auto columns = row.value().get_columns();
EXPECT_EQ(std::size_t(2U), columns.size());
EXPECT_STREQ("column1", columns[0U].get_name().c_str());
EXPECT_STREQ(value1.c_str(),
columns[0U].get_value<std::string>().c_str());
EXPECT_STREQ("column2", columns[1U].get_name().c_str());
EXPECT_STREQ(value2.c_str(),
columns[1U].get_value<std::string>().c_str());
for (auto &&column : columns) {
std::cout << column.get_index() << ':';
std::cout << column.get_name() << ':';
std::cout << column.get_value<std::string>() << std::endl;
}
}
++row_count;
}
EXPECT_EQ(std::size_t(1U), row_count);
}
static void common_delete(sqlite3 &db3, bool dump = false) {
{
auto query = utils::db::sqlite::db_delete{db3, "table"};
if (dump) {
std::cout << query.dump() << std::endl;
}
auto res = query.go();
EXPECT_TRUE(res.ok());
}
{
auto query = utils::db::sqlite::db_select{db3, "table"};
auto res = query.go();
EXPECT_TRUE(res.ok());
std::size_t row_count{};
while (res.has_row()) {
++row_count;
}
EXPECT_EQ(std::size_t(0U), row_count);
}
}
TEST_F(utils_db_sqlite, db_delete_query) {
auto query = utils::db::sqlite::db_delete{*db3.get(), "table"};
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(R"(DELETE FROM "table";)", query_str.c_str());
}
TEST_F(utils_db_sqlite, db_delete_where_query) {
auto query = utils::db::sqlite::db_delete{*db3.get(), "table"}
.where("column1")
.equals("test1")
.and_()
.where("column2")
.equals("test2");
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(R"(DELETE FROM "table" WHERE "column1"=?1 AND "column2"=?2;)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, db_insert_query) {
auto query = utils::db::sqlite::db_insert{*db3.get(), "table"}
.column_value("column1", "test9")
.column_value("column2", "test9");
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(R"(INSERT INTO "table" ("column1", "column2") VALUES (?1, ?2);)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, db_insert_or_replace_query) {
auto query = utils::db::sqlite::db_insert{*db3.get(), "table"}
.or_replace()
.column_value("column1", "test1")
.column_value("column2", "test2");
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(
R"(INSERT OR REPLACE INTO "table" ("column1", "column2") VALUES (?1, ?2);)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, db_select_query) {
auto query = utils::db::sqlite::db_select{*db3.get(), "table"};
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(R"(SELECT * FROM "table";)", query_str.c_str());
}
TEST_F(utils_db_sqlite, db_select_where_query) {
auto query = utils::db::sqlite::db_select{*db3.get(), "table"}
.where("column1")
.equals("test1")
.and_()
.where("column2")
.equals("test2");
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(R"(SELECT * FROM "table" WHERE "column1"=?1 AND "column2"=?2;)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, db_select_where_with_group_query) {
auto query =
utils::db::sqlite::db_select{*db3.get(), "table"}
.group([](auto &grp) {
grp.where("column1").equals("a").or_().where("column1").equals("b");
})
.and_()
.group([](auto &grp) {
grp.where("column2").equals("c").or_().where("column2").equals("d");
})
.or_()
.group([](auto &grp) {
grp.where("column1").equals("e").or_().where("column2").equals("f");
});
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(
R"(SELECT * FROM "table" WHERE ("column1"=?1 OR "column1"=?2) AND ("column2"=?3 OR "column2"=?4) OR ("column1"=?5 OR "column2"=?6);)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, db_select_columns_query) {
auto query = utils::db::sqlite::db_select{*db3.get(), "table"}
.column("column1")
.column("column2")
.where("column1")
.equals("test1")
.and_()
.where("column2")
.equals("test2");
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(
R"(SELECT column1, column2 FROM "table" WHERE "column1"=?1 AND "column2"=?2;)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, db_update_query) {
auto query = utils::db::sqlite::db_update{*db3.get(), "table"}
.column_value("column1", "moose")
.where("column1")
.equals("test1")
.and_()
.where("column2")
.equals("test2");
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(
R"(UPDATE "table" SET "column1"=?1 WHERE "column1"=?2 AND "column2"=?3;)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, insert_select_delete) {
common_insert(*db3.get(), true);
common_select(*db3.get(), "test0", "test1", true);
common_delete(*db3.get(), true);
}
TEST_F(utils_db_sqlite, insert_update_delete) {
common_insert(*db3.get());
{
auto query = utils::db::sqlite::db_update{*db3.get(), "table"}
.column_value("column1", "moose")
.where("column1")
.equals("test0");
std::cout << query.dump() << std::endl;
auto res = query.go();
EXPECT_TRUE(res.ok());
}
common_select(*db3.get(), "moose", "test1");
common_delete(*db3.get());
}
TEST_F(utils_db_sqlite, insert_or_replace_and_delete) {
common_insert(*db3.get());
{
auto query = utils::db::sqlite::db_insert{*db3.get(), "table"}
.or_replace()
.column_value("column1", "test0")
.column_value("column2", "moose");
std::cout << query.dump() << std::endl;
auto res = query.go();
EXPECT_TRUE(res.ok());
}
common_select(*db3.get(), "test0", "moose");
common_delete(*db3.get());
}
} // namespace monitarr
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@@ -0,0 +1,225 @@
/*
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 "test.hpp"
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
namespace {
const auto get_stop_requested = []() -> bool { return false; };
} // namespace
namespace monitarr {
TEST(utils_encrypting_reader, read_file_data) {
const auto token = std::string("moose");
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token);
for (std::uint8_t i = 0U; i < 8U; i++) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size());
for (std::uint8_t j = 0U; j < 2U; j++) {
ASSERT_EQ(
buffer.size() / 2U,
utils::encryption::encrypting_reader::reader_function(
reinterpret_cast<char *>(&buffer[(buffer.size() / 2U) * j]),
buffer.size() / 2U, 1U, &reader));
}
data_buffer decrypted_data;
EXPECT_TRUE(
utils::encryption::decrypt_data(token, buffer, decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
utils::encryption::encrypting_reader::get_data_chunk_size() * i,
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
TEST(utils_encrypting_reader, read_file_data_in_multiple_chunks) {
const auto token = std::string("moose");
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token);
for (std::uint8_t i = 0U; i < 8U; i += 2U) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size() *
2U);
EXPECT_EQ(buffer.size(),
utils::encryption::encrypting_reader::reader_function(
reinterpret_cast<char *>(buffer.data()), buffer.size(), 1U,
&reader));
for (std::uint8_t j = 0U; j < 2U; j++) {
data_buffer decrypted_data;
const auto offset = (j * (buffer.size() / 2U));
EXPECT_TRUE(utils::encryption::decrypt_data(
token,
data_buffer(
std::next(buffer.begin(), static_cast<std::int64_t>(offset)),
std::next(buffer.begin(), static_cast<std::int64_t>(
offset + (buffer.size() / 2U)))),
decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
(utils::encryption::encrypting_reader::get_data_chunk_size() * i) +
(j *
utils::encryption::encrypting_reader::get_data_chunk_size()),
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
}
TEST(utils_encrypting_reader, read_file_data_as_stream) {
const auto token = std::string("moose");
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token);
auto io_stream = reader.create_iostream();
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail());
EXPECT_TRUE(io_stream->good());
EXPECT_EQ(reader.get_total_size(),
static_cast<std::uint64_t>(io_stream->tellg()));
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::beg).fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t i = 0U; i < 8U; i++) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size());
EXPECT_FALSE(
io_stream->seekg(static_cast<std::streamoff>(i * buffer.size()))
.fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t j = 0U; j < 2U; j++) {
EXPECT_FALSE(
io_stream
->read(
reinterpret_cast<char *>(&buffer[(buffer.size() / 2U) * j]),
static_cast<std::streamsize>(buffer.size()) / 2U)
.fail());
EXPECT_TRUE(io_stream->good());
}
data_buffer decrypted_data;
EXPECT_TRUE(
utils::encryption::decrypt_data(token, buffer, decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
utils::encryption::encrypting_reader::get_data_chunk_size() * i,
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
TEST(utils_encrypting_reader, read_file_data_in_multiple_chunks_as_stream) {
const auto token = std::string("moose");
auto &source_file = test::create_random_file(
8u * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token);
auto io_stream = reader.create_iostream();
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail());
EXPECT_TRUE(io_stream->good());
EXPECT_EQ(reader.get_total_size(),
static_cast<std::uint64_t>(io_stream->tellg()));
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::beg).fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t i = 0U; i < 8U; i += 2U) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size() *
2U);
EXPECT_FALSE(io_stream
->read(reinterpret_cast<char *>(buffer.data()),
static_cast<std::streamsize>(buffer.size()))
.fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t j = 0U; j < 2U; j++) {
data_buffer decrypted_data;
const auto offset = (j * (buffer.size() / 2U));
EXPECT_TRUE(utils::encryption::decrypt_data(
token,
data_buffer(
std::next(buffer.begin(), static_cast<std::int64_t>(offset)),
std::next(buffer.begin(), static_cast<std::int64_t>(
offset + (buffer.size() / 2U)))),
decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
(utils::encryption::encrypting_reader::get_data_chunk_size() * i) +
(j *
utils::encryption::encrypting_reader::get_data_chunk_size()),
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
}
} // namespace monitarr
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)

View File

@@ -0,0 +1,286 @@
/*
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 "test.hpp"
#if defined(PROJECT_ENABLE_LIBSODIUM)
namespace {
const auto get_stop_requested = []() -> bool { return false; };
} // namespace
namespace monitarr {
static constexpr const std::string_view token{"moose"};
static constexpr const std::wstring_view token_w{L"moose"};
TEST(utils_encryption, generate_key) {
auto key1 =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
EXPECT_STREQ(
"ab4a0b004e824962913f7c0f79582b6ec7a3b8726426ca61d1a0a28ce5049e96",
utils::collection::to_hex_string(key1).c_str());
auto key2 =
utils::encryption::generate_key<utils::encryption::hash_256_t>("moose");
auto key3 =
utils::encryption::generate_key<utils::encryption::hash_256_t>("moose");
EXPECT_EQ(key2, key3);
auto key4 =
utils::encryption::generate_key<utils::encryption::hash_256_t>("moose2");
EXPECT_NE(key2, key4);
auto key1_w =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token_w);
EXPECT_NE(key1, key1_w);
#if defined(_WIN32)
EXPECT_STREQ(
L"4f5eb2a2ab34e3777b230465283923080b9ba59311e74058ccd74185131d11fe",
utils::collection::to_hex_wstring(key1_w).c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
L"0392d95ed3eee9772fbb9af68fedf829a8eb0adbe8575d9691cc9a752196766a",
utils::collection::to_hex_wstring(key1_w).c_str());
#endif
auto key2_w =
utils::encryption::generate_key<utils::encryption::hash_256_t>(L"moose");
auto key3_w =
utils::encryption::generate_key<utils::encryption::hash_256_t>(L"moose");
EXPECT_EQ(key2_w, key3_w);
EXPECT_NE(key2_w, key2);
EXPECT_NE(key3_w, key3);
auto key4_w =
utils::encryption::generate_key<utils::encryption::hash_256_t>(L"moose2");
EXPECT_NE(key2_w, key4_w);
EXPECT_NE(key4_w, key4);
}
TEST(utils_encryption, generate_key_default_hasher_is_blake2b_256) {
auto key1 =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
auto key2 = utils::encryption::generate_key<utils::encryption::hash_256_t>(
token, [](auto &&data, auto &&size) -> auto {
return utils::encryption::create_hash_blake2b_256(
std::string_view(reinterpret_cast<const char *>(data), size));
});
EXPECT_EQ(key1, key2);
auto key1_w =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token_w);
auto key2_w = utils::encryption::generate_key<utils::encryption::hash_256_t>(
token_w, [](auto &&data, auto &&size) -> auto {
return utils::encryption::create_hash_blake2b_256(std::wstring_view(
reinterpret_cast<const wchar_t *>(data), size / sizeof(wchar_t)));
});
EXPECT_EQ(key1_w, key2_w);
EXPECT_NE(key1_w, key1);
EXPECT_NE(key2_w, key2);
}
TEST(utils_encryption, generate_key_with_hasher) {
auto key1 = utils::encryption::generate_key<utils::encryption::hash_256_t>(
token, utils::encryption::blake2b_256_hasher);
EXPECT_STREQ(
"ab4a0b004e824962913f7c0f79582b6ec7a3b8726426ca61d1a0a28ce5049e96",
utils::collection::to_hex_string(key1).c_str());
auto key2 = utils::encryption::generate_key<utils::encryption::hash_256_t>(
token, utils::encryption::sha256_hasher);
EXPECT_NE(key1, key2);
EXPECT_STREQ(
"182072537ada59e4d6b18034a80302ebae935f66adbdf0f271d3d36309c2d481",
utils::collection::to_hex_string(key2).c_str());
auto key1_w = utils::encryption::generate_key<utils::encryption::hash_256_t>(
token_w, utils::encryption::blake2b_256_hasher);
#if defined(_WIN32)
EXPECT_STREQ(
L"4f5eb2a2ab34e3777b230465283923080b9ba59311e74058ccd74185131d11fe",
utils::collection::to_hex_wstring(key1_w).c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
L"0392d95ed3eee9772fbb9af68fedf829a8eb0adbe8575d9691cc9a752196766a",
utils::collection::to_hex_wstring(key1_w).c_str());
#endif
auto key2_w = utils::encryption::generate_key<utils::encryption::hash_256_t>(
token_w, utils::encryption::sha256_hasher);
EXPECT_NE(key1_w, key2_w);
#if defined(_WIN32)
EXPECT_STREQ(
L"918e4c6d39bb373f139b5fac8ec0548a9770da399b2835608974ffeac7fab6c4",
utils::collection::to_hex_wstring(key2_w).c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
L"590ac70125bec4501172937f6a2cbdeb22a87b5e40d5595eccd06b2b20548d8f",
utils::collection::to_hex_wstring(key2_w).c_str());
#endif
EXPECT_NE(key1_w, key1);
EXPECT_NE(key2_w, key2);
}
#if defined(PROJECT_ENABLE_BOOST)
static const std::string buffer = "cow moose dog chicken";
static void test_encrypted_result(const data_buffer &result) {
EXPECT_EQ(buffer.size() + utils::encryption::encryption_header_size,
result.size());
std::string data;
EXPECT_TRUE(utils::encryption::decrypt_data(token, result, data));
EXPECT_EQ(buffer.size(), data.size());
EXPECT_STREQ(buffer.c_str(), data.c_str());
}
TEST(utils_encryption, encrypt_data_buffer) {
data_buffer result;
utils::encryption::encrypt_data(token, buffer, result);
test_encrypted_result(result);
}
TEST(utils_encryption, encrypt_data_buffer_with_key) {
const auto key =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(key, buffer, result);
test_encrypted_result(result);
}
TEST(utils_encryption, encrypt_data_pointer) {
data_buffer result;
utils::encryption::encrypt_data(
token, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
test_encrypted_result(result);
}
TEST(utils_encryption, encrypt_data_pointer_with_key) {
const auto key =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(
key, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
test_encrypted_result(result);
}
TEST(utils_encryption, decrypt_data_pointer) {
const auto key =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(
key, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
std::string data;
EXPECT_TRUE(utils::encryption::decrypt_data(token, result.data(),
result.size(), data));
EXPECT_EQ(buffer.size(), data.size());
EXPECT_STREQ(buffer.c_str(), data.c_str());
}
TEST(utils_encryption, decrypt_data_buffer_with_key) {
const auto key =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(
key, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
std::string data;
EXPECT_TRUE(utils::encryption::decrypt_data(key, result, data));
EXPECT_EQ(buffer.size(), data.size());
EXPECT_STREQ(buffer.c_str(), data.c_str());
}
TEST(utils_encryption, decrypt_data_pointer_with_key) {
const auto key =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(
key, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
std::string data;
EXPECT_TRUE(
utils::encryption::decrypt_data(key, result.data(), result.size(), data));
EXPECT_EQ(buffer.size(), data.size());
EXPECT_STREQ(buffer.c_str(), data.c_str());
}
TEST(utils_encryption, decryption_failure) {
const auto key =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(
key, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
result[0U] = 0U;
result[1U] = 1U;
result[2U] = 2U;
std::string data;
EXPECT_FALSE(utils::encryption::decrypt_data(key, result, data));
}
TEST(utils_encryption, decrypt_file_name) {
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token,
std::nullopt);
auto file_name = reader.get_encrypted_file_name();
EXPECT_EQ(true, utils::encryption::decrypt_file_name(token, file_name));
EXPECT_STREQ("test.dat", file_name.c_str());
}
}
TEST(utils_encryption, decrypt_file_path) {
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token,
"moose/cow");
auto file_path = reader.get_encrypted_file_path();
EXPECT_EQ(true, utils::encryption::decrypt_file_path(token, file_path));
EXPECT_STREQ("/moose/cow/test.dat", file_path.c_str());
}
}
#endif // defined(PROJECT_ENABLE_BOOST)
} // namespace monitarr
#endif // defined(PROJECT_ENABLE_LIBSODIUM)

View File

@@ -0,0 +1,73 @@
/*
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 "test.hpp"
namespace monitarr {
template <typename T, typename U>
constexpr bool is_decay_equ = std::is_same_v<std::decay_t<T>, U>;
TEST(utils_error, check_default_exception_handler) {
EXPECT_TRUE(utils::error::get_exception_handler() != nullptr);
if (&utils::error::default_exception_handler ==
utils::error::get_exception_handler()) {
auto default_handler_is_iostream =
is_decay_equ<decltype(utils::error::default_exception_handler),
utils::error::iostream_exception_handler>;
EXPECT_TRUE(default_handler_is_iostream);
}
}
TEST(utils_error, can_override_exception_handler) {
struct my_exc_handler final : public utils::error::i_exception_handler {
MOCK_METHOD(void, handle_error,
(std::string_view function_name, std::string_view msg),
(const, override));
MOCK_METHOD(void, handle_exception, (std::string_view function_name),
(const, override));
MOCK_METHOD(void, handle_exception,
(std::string_view function_name, const std::exception &ex),
(const, override));
};
my_exc_handler handler{};
utils::error::set_exception_handler(&handler);
EXPECT_CALL(handler, handle_error("test_func", "error")).WillOnce(Return());
utils::error::handle_error("test_func", "error");
EXPECT_CALL(handler, handle_exception("test_func")).WillOnce(Return());
utils::error::handle_exception("test_func");
auto ex = std::runtime_error("moose");
EXPECT_CALL(handler, handle_exception(_, _))
.WillOnce(
[&ex](std::string_view function_name, const std::exception &ex2) {
EXPECT_EQ("test_func_ex", function_name);
EXPECT_STREQ(ex.what(), ex2.what());
});
utils::error::handle_exception("test_func_ex", ex);
utils::error::set_exception_handler(&utils::error::default_exception_handler);
}
} // namespace monitarr

View File

@@ -0,0 +1,556 @@
/*
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 "test.hpp"
namespace {
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#include "utils/file_enc_file.hpp"
constexpr const auto file_type_count{3U};
#else
constexpr const auto file_type_count{2U};
#endif
[[nodiscard]] auto create_file(auto idx, auto path,
bool read_only = false) -> auto {
switch (idx) {
case 0U:
return monitarr::utils::file::file::open_or_create_file(path, read_only);
case 1U:
return monitarr::utils::file::thread_file::open_or_create_file(path,
read_only);
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
case 2U:
return monitarr::utils::file::enc_file::attach_file(
monitarr::utils::file::file::open_or_create_file(path, read_only));
#endif
default:
throw std::runtime_error("not supported");
}
}
[[nodiscard]] auto open_file(auto idx, auto path,
bool read_only = false) -> auto {
switch (idx) {
case 0U:
return monitarr::utils::file::file::open_file(path, read_only);
case 1U:
return monitarr::utils::file::thread_file::open_file(path, read_only);
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
case 2U:
return monitarr::utils::file::enc_file::attach_file(
monitarr::utils::file::file::open_file(path, read_only));
#endif
default:
throw std::runtime_error("not supported");
}
}
} // namespace
namespace monitarr {
TEST(utils_file, can_create_and_remove_file) {
for (auto idx = 0U; idx < file_type_count; ++idx) {
auto path = test::generate_test_file_name("utils_file");
EXPECT_FALSE(utils::file::file(path).exists() ||
utils::file::directory(path).exists());
auto file{create_file(idx, path)};
EXPECT_TRUE(*file);
EXPECT_TRUE(utils::file::file(path).exists());
EXPECT_TRUE(file->exists());
EXPECT_TRUE(file->remove());
EXPECT_FALSE(utils::file::file(path).exists());
EXPECT_FALSE(file->exists());
}
}
TEST(utils_file, can_open_file) {
for (auto idx = 0U; idx < file_type_count; ++idx) {
auto path = test::generate_test_file_name("utils_file");
{
auto file{create_file(idx, path)};
EXPECT_TRUE(*file);
}
{
auto file{create_file(idx, path)};
EXPECT_TRUE(*file);
}
}
}
TEST(utils_file, open_file_fails_if_not_found) {
for (auto idx = 0U; idx < file_type_count; ++idx) {
auto path = test::generate_test_file_name("utils_file");
auto file{open_file(idx, path)};
EXPECT_FALSE(*file);
}
}
TEST(utils_file, write_fails_for_read_only_file) {
for (auto idx = 0U; idx < file_type_count; ++idx) {
auto path = test::generate_test_file_name("utils_file");
auto file{create_file(idx, path, true)};
EXPECT_TRUE(utils::file::file(path).exists());
EXPECT_TRUE(*file);
std::size_t bytes_written{};
EXPECT_FALSE(file->write(reinterpret_cast<const unsigned char *>("0"), 1U,
0U, &bytes_written));
EXPECT_EQ(0U, bytes_written);
}
}
// TEST(utils_file, can_attach_file) {
// for (auto idx = 0U; idx < file_type_count; ++idx) {
// auto path = test::generate_test_file_name("utils_file");
// auto file = idx == 0U ? utils::file::file::open_or_create_file(path)
// :
// utils::file::thread_file::open_or_create_file(path);
// auto file2 =
// idx == 0U ? utils::file::file::attach_file(file->get_handle())
// :
// utils::file::thread_file::attach_file(file->get_handle());
// EXPECT_TRUE(*file);
// EXPECT_TRUE(*file2);
// EXPECT_EQ(file->get_path(), file2->get_path());
// }
// }
#if defined(PROJECT_ENABLE_JSON)
TEST(utils_file, read_and_write_json_file) {
auto path = test::generate_test_file_name("utils_file");
auto json_data = nlohmann::json({{"moose", "cow"}});
EXPECT_TRUE(utils::file::write_json_file(path, json_data));
{
nlohmann::json result_data{};
EXPECT_TRUE(utils::file::read_json_file(path, result_data));
EXPECT_STREQ(json_data.dump().c_str(), result_data.dump().c_str());
}
{
nlohmann::json result_data{};
EXPECT_TRUE(utils::file::read_json_file(utils::string::from_utf8(path),
result_data));
EXPECT_STREQ(json_data.dump().c_str(), result_data.dump().c_str());
}
}
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
TEST(utils_file, read_and_write_json_file_encrypted) {
{
auto path = test::generate_test_file_name("utils_file");
auto json_data = nlohmann::json({{"moose", "cow"}});
EXPECT_TRUE(utils::file::write_json_file(path, json_data, "moose"));
nlohmann::json result_data{};
EXPECT_TRUE(utils::file::read_json_file(path, result_data, "moose"));
EXPECT_STREQ(json_data.dump().c_str(), result_data.dump().c_str());
{
auto file = utils::file::file::open_file(path);
data_buffer encrypted_data{};
EXPECT_TRUE(file->read_all(encrypted_data, 0U));
data_buffer decrypted_data{};
EXPECT_TRUE(utils::encryption::decrypt_data("moose", encrypted_data,
decrypted_data));
EXPECT_STREQ(json_data.dump().c_str(),
nlohmann::json::parse(std::string(decrypted_data.begin(),
decrypted_data.end()))
.dump()
.c_str());
}
}
{
auto path =
utils::string::from_utf8(test::generate_test_file_name("utils_file"));
auto json_data = nlohmann::json({{"moose", "cow"}});
EXPECT_TRUE(utils::file::write_json_file(path, json_data, L"moose"));
nlohmann::json result_data{};
EXPECT_TRUE(utils::file::read_json_file(path, result_data, L"moose"));
EXPECT_STREQ(json_data.dump().c_str(), result_data.dump().c_str());
{
auto file = utils::file::file::open_file(path);
data_buffer encrypted_data{};
EXPECT_TRUE(file->read_all(encrypted_data, 0U));
data_buffer decrypted_data{};
EXPECT_TRUE(utils::encryption::decrypt_data("moose", encrypted_data,
decrypted_data));
EXPECT_STREQ(json_data.dump().c_str(),
nlohmann::json::parse(std::string(decrypted_data.begin(),
decrypted_data.end()))
.dump()
.c_str());
}
}
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#endif // defined(PROJECT_ENABLE_JSON)
#if defined(PROJECT_ENABLE_LIBDSM)
TEST(utils_file, smb_create_smb_path) {
const auto *path = "//server/share";
const auto *rel_path = "test/test.txt";
auto smb_path = utils::file::smb_create_smb_path(path, rel_path);
EXPECT_STREQ("//server/share/test/test.txt", smb_path.c_str());
rel_path = "/test/test.txt";
smb_path = utils::file::smb_create_smb_path(path, rel_path);
EXPECT_STREQ("//server/share/test/test.txt", smb_path.c_str());
rel_path = "test\\test.txt";
smb_path = utils::file::smb_create_smb_path(path, rel_path);
EXPECT_STREQ("//server/share/test/test.txt", smb_path.c_str());
rel_path = "\\test\\test.txt";
smb_path = utils::file::smb_create_smb_path(path, rel_path);
EXPECT_STREQ("//server/share/test/test.txt", smb_path.c_str());
}
TEST(utils_file, smb_create_relative_path) {
const auto *path = "//server/share/test.txt";
auto rel_path = utils::file::smb_create_relative_path(path);
EXPECT_STREQ("\\test.txt", rel_path.c_str());
path = "//server/share/test";
rel_path = utils::file::smb_create_relative_path(path);
EXPECT_STREQ("\\test", rel_path.c_str());
path = "//server/share/test/";
rel_path = utils::file::smb_create_relative_path(path);
EXPECT_STREQ("\\test", rel_path.c_str());
path = "//server/share/test/";
rel_path = utils::file::smb_create_relative_path(path);
EXPECT_STREQ("\\test", rel_path.c_str());
}
TEST(utils_file, smb_create_search_path) {
const auto *path = "//server/share";
auto search_path = utils::file::smb_create_search_path(path);
EXPECT_STREQ("\\*", search_path.c_str());
path = "//server/share/";
search_path = utils::file::smb_create_search_path(path);
EXPECT_STREQ("\\*", search_path.c_str());
path = "//server/share/folder";
search_path = utils::file::smb_create_search_path(path);
EXPECT_STREQ("\\folder\\*", search_path.c_str());
path = "//server/share/folder/";
search_path = utils::file::smb_create_search_path(path);
EXPECT_STREQ("\\folder\\*", search_path.c_str());
path = "//server/share/folder/next";
search_path = utils::file::smb_create_search_path(path);
EXPECT_STREQ("\\folder\\next\\*", search_path.c_str());
path = "//server/share/folder/next/";
search_path = utils::file::smb_create_search_path(path);
EXPECT_STREQ("\\folder\\next\\*", search_path.c_str());
}
TEST(utils_file, smb_parent_is_same) {
const auto *path1 = "//server/share";
const auto *path2 = "//server/share";
EXPECT_TRUE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server/share/";
path2 = "//server/share/";
EXPECT_TRUE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server/share/one";
path2 = "//server/share/two";
EXPECT_TRUE(utils::file::smb_parent_is_same(path1, path2));
path1 = "// server/cow";
path2 = "// server/cow";
EXPECT_TRUE(utils::file::smb_parent_is_same(path1, path2));
}
TEST(utils_file, smb_parent_is_not_same) {
const auto *path1 = "server/share";
const auto *path2 = "//server/share";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "server/share/";
path2 = "server/share/";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server1/share/one";
path2 = "//server/share/two";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server/share";
path2 = "//server/share2";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server/share/";
path2 = "//server/share2/";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server/share/one";
path2 = "//server/share2/two";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server";
path2 = "//server/share/two";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server/";
path2 = "//server/";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server";
path2 = "//server";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
}
#endif // defined(PROJECT_ENABLE_LIBDSM)
TEST(utils_file, directory_exists_in_path) {
auto &test_dir = test::generate_test_directory();
EXPECT_FALSE(
utils::file::directory_exists_in_path(test_dir.get_path(), "moose"));
EXPECT_FALSE(utils::file::directory_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose"));
EXPECT_FALSE(utils::file::file_exists_in_path(test_dir.get_path(), "moose"));
EXPECT_FALSE(utils::file::file_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose"));
auto sub_dir = test_dir.create_directory("moose");
EXPECT_TRUE(sub_dir != nullptr);
if (sub_dir) {
EXPECT_TRUE(
utils::file::directory_exists_in_path(test_dir.get_path(), "moose"));
EXPECT_FALSE(
utils::file::file_exists_in_path(test_dir.get_path(), "moose"));
EXPECT_TRUE(utils::file::directory_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose"));
EXPECT_FALSE(utils::file::file_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose"));
}
}
TEST(utils_file, file_exists_in_path) {
auto &test_dir = test::generate_test_directory();
EXPECT_FALSE(
utils::file::file_exists_in_path(test_dir.get_path(), "moose.txt"));
EXPECT_FALSE(utils::file::file_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose.txt"));
EXPECT_FALSE(
utils::file::directory_exists_in_path(test_dir.get_path(), "moose.txt"));
EXPECT_FALSE(utils::file::directory_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose.txt"));
auto sub_file = test_dir.create_file("moose.txt", false);
EXPECT_TRUE(sub_file != nullptr);
if (sub_file) {
EXPECT_TRUE(
utils::file::file_exists_in_path(test_dir.get_path(), "moose.txt"));
EXPECT_FALSE(utils::file::directory_exists_in_path(test_dir.get_path(),
"moose.txt"));
EXPECT_TRUE(utils::file::file_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose.txt"));
EXPECT_FALSE(utils::file::directory_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose.txt"));
}
}
TEST(utils_file, get_free_drive_space) {
#if defined(_WIN32)
auto space = utils::file::get_free_drive_space("C:");
auto space2 = utils::file::get_free_drive_space(L"C:");
#else // defined(_WIN32)
auto space = utils::file::get_free_drive_space("/");
auto space2 = utils::file::get_free_drive_space(L"/");
#endif // !defined(_WIN32)
EXPECT_TRUE(space.has_value());
EXPECT_LT(0U, space.value());
EXPECT_TRUE(space2.has_value());
EXPECT_EQ(space.value(), space2.value());
}
TEST(utils_file, get_free_drive_space_fails_for_bad_path) {
std::string name{"free_drive_space_test_XXXXXX"};
auto temp = utils::file::create_temp_name("free_drive_space_test");
auto space = utils::file::get_free_drive_space(temp);
EXPECT_FALSE(space.has_value());
}
TEST(utils_file, get_total_drive_space) {
#if defined(_WIN32)
auto space = utils::file::get_total_drive_space("C:");
auto space2 = utils::file::get_total_drive_space(L"C:");
#else // defined(_WIN32)
auto space = utils::file::get_total_drive_space("/");
auto space2 = utils::file::get_total_drive_space(L"/");
#endif // !defined(_WIN32)
EXPECT_TRUE(space.has_value());
EXPECT_LT(0U, space.value());
EXPECT_TRUE(space2.has_value());
EXPECT_EQ(space.value(), space2.value());
}
TEST(utils_file, create_temp_name) {
{
auto temp = utils::file::create_temp_name("test_temp");
EXPECT_EQ(18U, temp.size());
auto temp2 = utils::file::create_temp_name("test_temp");
EXPECT_STRNE(temp.c_str(), temp2.c_str());
EXPECT_TRUE(utils::string::begins_with(temp, "test_temp_"));
}
{
auto temp = utils::file::create_temp_name(L"test_temp");
EXPECT_EQ(18U, temp.size());
auto temp2 = utils::file::create_temp_name(L"test_temp");
EXPECT_STRNE(temp.c_str(), temp2.c_str());
EXPECT_TRUE(utils::string::begins_with(temp, L"test_temp_"));
}
}
TEST(utils_file, get_total_drive_space_fails_for_bad_path) {
auto temp = utils::file::create_temp_name("total_drive_space_test");
auto space = utils::file::get_total_drive_space(temp);
EXPECT_FALSE(space.has_value());
}
TEST(utils_file, get_times) {
{
auto times =
utils::file::get_times(test::create_random_file(1U).get_path());
EXPECT_TRUE(times.has_value());
EXPECT_LT(0U, times->get(utils::file::time_type::accessed));
EXPECT_LT(0U, times->get(utils::file::time_type::created));
EXPECT_LT(0U, times->get(utils::file::time_type::modified));
EXPECT_LT(0U, times->get(utils::file::time_type::written));
}
{
auto times = utils::file::get_times(
utils::string::from_utf8(test::create_random_file(1U).get_path()));
EXPECT_TRUE(times.has_value());
EXPECT_LT(0U, times->get(utils::file::time_type::accessed));
EXPECT_LT(0U, times->get(utils::file::time_type::created));
EXPECT_LT(0U, times->get(utils::file::time_type::modified));
EXPECT_LT(0U, times->get(utils::file::time_type::written));
}
}
TEST(utils_file, get_times_fails_if_not_found) {
auto temp = utils::path::combine(".", {"get_times_test"});
auto times = utils::file::get_times(temp);
EXPECT_FALSE(times.has_value());
}
TEST(utils_file, get_time) {
{
auto file_path = test::create_random_file(1U).get_path();
auto file_time =
utils::file::get_time(file_path, utils::file::time_type::accessed);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::created);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::modified);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::written);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
}
{
auto file_path =
utils::string::from_utf8(test::create_random_file(1U).get_path());
auto file_time =
utils::file::get_time(file_path, utils::file::time_type::accessed);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::created);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::modified);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::written);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
}
}
TEST(utils_file, get_time_fails_if_not_found) {
auto temp = utils::path::combine(".", {"get_times_test"});
auto file_time =
utils::file::get_time(temp, utils::file::time_type::accessed);
EXPECT_FALSE(file_time.has_value());
}
} // namespace monitarr

View File

@@ -0,0 +1,186 @@
/*
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 "test.hpp"
#if defined(PROJECT_ENABLE_LIBSODIUM)
namespace monitarr {
TEST(utils_hash, hash_type_sizes) {
EXPECT_EQ(32U, utils::encryption::hash_256_t{}.size());
EXPECT_EQ(48U, utils::encryption::hash_384_t{}.size());
EXPECT_EQ(64U, utils::encryption::hash_512_t{}.size());
}
TEST(utils_hash, default_hasher_is_blake2b) {
EXPECT_EQ(
&utils::encryption::blake2b_256_hasher,
&utils::encryption::default_create_hash<utils::encryption::hash_256_t>());
EXPECT_EQ(
&utils::encryption::blake2b_384_hasher,
&utils::encryption::default_create_hash<utils::encryption::hash_384_t>());
EXPECT_EQ(
&utils::encryption::blake2b_512_hasher,
&utils::encryption::default_create_hash<utils::encryption::hash_512_t>());
}
TEST(utils_hash, blake2b_256) {
auto hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_256("a"));
EXPECT_STREQ(
"8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4",
hash.c_str());
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_256(L"a"));
#if defined(_WIN32)
EXPECT_STREQ(
"d2373b17cd8a8e19e39f52fa4905a274f93805fbb8bb4c7f3cb4b2cd6708ec8a",
hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
"9fdf5757d7eea386f0d34d2c0e202527986febf1ebb4315fcf7fff40776fa41d",
hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_256({1U}));
EXPECT_STREQ(
"ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25",
hash.c_str());
}
TEST(utils_hash, blake2b_384) {
auto hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_384("a"));
EXPECT_STREQ("7d40de16ff771d4595bf70cbda0c4ea0a066a6046fa73d34471cd4d93d827d7"
"c94c29399c50de86983af1ec61d5dcef0",
hash.c_str());
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_384(L"a"));
#if defined(_WIN32)
EXPECT_STREQ("637fe31d1e955760ef31043d525d9321826a778ddbe82fcde45a98394241380"
"96675e2f87e36b53ab223a7fd254198fd",
hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ("9d469bd9dab9d4b48b8688de7c22704a8de1b081294f9be294100dfa9f05c92"
"e8d3616476e46cd14f9e613fed80fd157",
hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_384({1U}));
EXPECT_STREQ("42cfe875d08d816538103b906bb0b05202e0b09c4e981680c1110684fc7845b"
"c91c178fa167afcc445490644b2bf5f5b",
hash.c_str());
}
TEST(utils_hash, blake2b_512) {
auto hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_512("a"));
EXPECT_STREQ(
"333fcb4ee1aa7c115355ec66ceac917c8bfd815bf7587d325aec1864edd24e34d5abe2c6"
"b1b5ee3face62fed78dbef802f2a85cb91d455a8f5249d330853cb3c",
hash.c_str());
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_512(L"a"));
#if defined(_WIN32)
EXPECT_STREQ(
"05970b95468b0b1941066ff189091493e73859ce41cde5ad08118e93ea1d81a57a144296"
"a26a9fe7781481bde97b886725e36e30b305d8bd5cce1ae36bf1564a",
hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
"bbc187c6e4d8525655d0ada62d16eed59f3db3ab07e04fb0483fd4ae21d88b984774add9"
"b3fbcff56f9638091013994f8e2d4646fdbbcb4879e2b5160bbb755d",
hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_512({1U}));
EXPECT_STREQ(
"9545ba37b230d8a2e716c4707586542780815b7c4088edcb9af6a9452d50f32474d5ba9a"
"ab52a67aca864ef2696981c2eadf49020416136afd838fb048d21653",
hash.c_str());
}
TEST(utils_hash, sha256) {
auto hash = utils::collection::to_hex_string(
utils::encryption::create_hash_sha256("a"));
EXPECT_STREQ(
"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb",
hash.c_str());
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_sha256(L"a"));
#if defined(_WIN32)
EXPECT_STREQ(
"ffe9aaeaa2a2d5048174df0b80599ef0197ec024c4b051bc9860cff58ef7f9f3",
hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
"a2d398922901344d08180dc41d3e9d73d8c148c7f6e092835bbb28e02dbcf184",
hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_sha256({1U}));
EXPECT_STREQ(
"4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a",
hash.c_str());
}
TEST(utils_hash, sha512) {
auto hash = utils::collection::to_hex_string(
utils::encryption::create_hash_sha512("a"));
EXPECT_STREQ(
"1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c65"
"2bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75",
hash.c_str());
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_sha512(L"a"));
#if defined(_WIN32)
EXPECT_STREQ(
"5c2ca3d50f46ece6066c53bd1a490cbe5f72d2738ae9417332e91e5c3f75205c639d71a9"
"a41d67d965fa137dddf439e0ab9443a6ea44915e90d8b5b566d1c076",
hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
"a93498d992e81915075144cb304d2bdf040b336283f888252244882d8366dd3a6e2d9749"
"077114dda1a9aa1a7b69d33f7a781f003ccd12e599a6341014f29aaf",
hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_sha512({1U}));
EXPECT_STREQ(
"7b54b66836c1fbdd13d2441d9e1434dc62ca677fb68f5fe66a464baadecdbd00576f8d6b"
"5ac3bcc80844b7d50b1cc6603444bbe7cfcf8fc0aa1ee3c636d9e339",
hash.c_str());
}
} // namespace monitarr
#endif // defined(PROJECT_ENABLE_LIBSODIUM)

View File

@@ -0,0 +1,552 @@
/*
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 "test.hpp"
#if defined(_WIN32)
namespace {
static const auto test_path = [](std::string str) -> std::string {
#if defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
if (monitarr::utils::string::begins_with(str, "\\")) {
str = monitarr::utils::string::to_lower(
std::filesystem::current_path().string().substr(0U, 2U)) +
str;
}
str = std::string{monitarr::utils::path::long_notation} + str;
#else // !defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
if (monitarr::utils::string::begins_with(str, "\\")) {
str = monitarr::utils::string::to_lower(
std::filesystem::current_path().string().substr(0U, 2U)) +
str;
}
#endif // defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
return monitarr::utils::string::right_trim(str, '\\');
};
} // namespace
#endif // defined(_WIN32)
namespace monitarr {
TEST(utils_path, constants) {
EXPECT_EQ(std::string_view{"\\"}, utils::path::backslash);
EXPECT_EQ(std::wstring_view{L"\\"}, utils::path::backslash_w);
EXPECT_EQ(std::string_view{"."}, utils::path::dot);
EXPECT_EQ(std::wstring_view{L"."}, utils::path::dot_w);
EXPECT_EQ(std::string_view{".\\"}, utils::path::dot_backslash);
EXPECT_EQ(std::wstring_view{L".\\"}, utils::path::dot_backslash_w);
EXPECT_EQ(std::string_view{"./"}, utils::path::dot_slash);
EXPECT_EQ(std::wstring_view{L"./"}, utils::path::dot_slash_w);
EXPECT_EQ(std::string_view{"/"}, utils::path::slash);
EXPECT_EQ(std::wstring_view{L"/"}, utils::path::slash_w);
#if defined(_WIN32)
EXPECT_EQ(std::string_view{"\\\\"}, utils::path::unc_notation);
EXPECT_EQ(std::wstring_view{L"\\\\"}, utils::path::unc_notation_w);
#endif // defined(_WIN32)
}
TEST(utils_path, directory_seperator) {
#if defined(_WIN32)
EXPECT_EQ(utils::path::backslash, utils::path::directory_seperator);
EXPECT_EQ(utils::path::backslash_w, utils::path::directory_seperator_w);
EXPECT_EQ(utils::path::slash, utils::path::not_directory_seperator);
EXPECT_EQ(utils::path::slash_w, utils::path::not_directory_seperator_w);
#else // !defined(_WIN32)
EXPECT_EQ(utils::path::slash, utils::path::directory_seperator);
EXPECT_EQ(utils::path::slash_w, utils::path::directory_seperator_w);
EXPECT_EQ(utils::path::backslash, utils::path::not_directory_seperator);
EXPECT_EQ(utils::path::backslash_w, utils::path::not_directory_seperator_w);
#endif // defined(_WIN32)
}
TEST(utils_path, get_directory_seperator) {
#if defined(_WIN32)
EXPECT_EQ(utils::path::backslash,
utils::path::get_directory_seperator<char>());
EXPECT_EQ(utils::path::backslash_w,
utils::path::get_directory_seperator<wchar_t>());
EXPECT_EQ(utils::path::slash,
utils::path::get_not_directory_seperator<char>());
EXPECT_EQ(utils::path::slash_w,
utils::path::get_not_directory_seperator<wchar_t>());
#else // !defined(_WIN32)
EXPECT_EQ(utils::path::slash, utils::path::get_directory_seperator<char>());
EXPECT_EQ(utils::path::slash_w,
utils::path::get_directory_seperator<wchar_t>());
EXPECT_EQ(utils::path::backslash,
utils::path::get_not_directory_seperator<char>());
EXPECT_EQ(utils::path::backslash_w,
utils::path::get_not_directory_seperator<wchar_t>());
#endif // defined(_WIN32)
}
TEST(utils_path, get_backslash) {
EXPECT_EQ(utils::path::backslash, utils::path::get_backslash<char>());
EXPECT_EQ(utils::path::backslash_w, utils::path::get_backslash<wchar_t>());
}
TEST(utils_path, get_dot) {
EXPECT_EQ(utils::path::dot, utils::path::get_dot<char>());
EXPECT_EQ(utils::path::dot_w, utils::path::get_dot<wchar_t>());
}
TEST(utils_path, get_dot_backslash) {
EXPECT_EQ(utils::path::dot_backslash, utils::path::get_dot_backslash<char>());
EXPECT_EQ(utils::path::dot_backslash_w,
utils::path::get_dot_backslash<wchar_t>());
}
TEST(utils_path, get_dot_slash) {
EXPECT_EQ(utils::path::dot_slash, utils::path::get_dot_slash<char>());
EXPECT_EQ(utils::path::dot_slash_w, utils::path::get_dot_slash<wchar_t>());
}
TEST(utils_path, get_slash) {
EXPECT_EQ(utils::path::slash, utils::path::get_slash<char>());
EXPECT_EQ(utils::path::slash_w, utils::path::get_slash<wchar_t>());
}
TEST(utils_path, get_long_notation) {
EXPECT_EQ(utils::path::long_notation, utils::path::get_long_notation<char>());
EXPECT_EQ(utils::path::long_notation_w,
utils::path::get_long_notation<wchar_t>());
}
TEST(utils_path, combine) {
auto s = utils::path::combine(R"(\test\path)", {});
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\test\path)").c_str(), s.c_str());
#else
EXPECT_STREQ("/test/path", s.c_str());
#endif
s = utils::path::combine(R"(\test)", {R"(\path)"});
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\test\path)").c_str(), s.c_str());
#else
EXPECT_STREQ("/test/path", s.c_str());
#endif
s = utils::path::combine(R"(\test)", {R"(\path)", R"(\again\)"});
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\test\path\again)").c_str(), s.c_str());
#else
EXPECT_STREQ("/test/path/again", s.c_str());
#endif
s = utils::path::combine("/home/test/.dest", {".state"});
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\home\test\.dest\.state)").c_str(), s.c_str());
#else
EXPECT_STREQ("/home/test/.dest/.state", s.c_str());
#endif
#if defined(_WIN32)
s = utils::path::combine(R"(R:\test)", {R"(\path)", R"(\again\)"});
EXPECT_STREQ(test_path(R"(r:\test\path\again)").c_str(), s.c_str());
s = utils::path::combine("R:", {R"(\path)", R"(\again\)"});
EXPECT_STREQ(test_path(R"(r:\path\again)").c_str(), s.c_str());
s = utils::path::combine("R:", {});
EXPECT_STREQ(test_path("r:").c_str(), s.c_str());
s = utils::path::combine("R:", {"\\"});
EXPECT_STREQ(test_path("r:").c_str(), s.c_str());
s = utils::path::combine("\\\\moose", {"cow"});
EXPECT_STREQ("\\\\moose\\cow", s.c_str());
#endif
}
TEST(utils_path, format_path) {
std::string path{"./"};
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ(".", path.c_str());
path = "~/.test";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("~/.test", path.c_str());
path = "\\";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "\\\\";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "\\\\\\";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "\\\\\\\\";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "/";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "//";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "///";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "////";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
}
TEST(utils_path, create_api_path) {
auto s = utils::path::create_api_path("");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path(R"(\)");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path("/");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path(".");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path("./");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path(R"(\\)");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path("//");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path("/cow///moose/////dog/chicken");
EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str());
s = utils::path::create_api_path("\\cow\\\\\\moose\\\\\\\\dog\\chicken/");
EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str());
s = utils::path::create_api_path("/cow\\\\/moose\\\\/\\dog\\chicken\\");
EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str());
s = utils::path::create_api_path(".state");
EXPECT_STREQ("/.state", s.c_str());
s = utils::path::create_api_path("/.state/.local");
EXPECT_STREQ("/.state/.local", s.c_str());
s = utils::path::create_api_path("./.state/.local");
EXPECT_STREQ("/.state/.local", s.c_str());
}
TEST(utils_path, get_parent_api_path) {
auto s = utils::path::get_parent_api_path("");
EXPECT_STREQ("/", s.c_str());
s = utils::path::get_parent_api_path("/");
EXPECT_STREQ("/", s.c_str());
s = utils::path::get_parent_api_path("/moose");
EXPECT_STREQ("/", s.c_str());
s = utils::path::get_parent_api_path("/moose/cow");
EXPECT_STREQ("/moose", s.c_str());
s = utils::path::get_parent_api_path("/moose/cow/");
EXPECT_STREQ("/moose", s.c_str());
}
TEST(utils_path, finalize) {
auto s = utils::path::finalize("");
EXPECT_STREQ("", s.c_str());
s = utils::path::finalize(R"(\)");
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\)").c_str(), s.c_str());
#else
EXPECT_STREQ("/", s.c_str());
#endif
s = utils::path::finalize("/");
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\)").c_str(), s.c_str());
#else
EXPECT_STREQ("/", s.c_str());
#endif
s = utils::path::finalize(R"(\\)");
#if defined(_WIN32)
EXPECT_STREQ("\\\\", s.c_str());
#else
EXPECT_STREQ("/", s.c_str());
#endif
s = utils::path::finalize("//");
#if defined(_WIN32)
EXPECT_STREQ("\\\\", s.c_str());
#else
EXPECT_STREQ("/", s.c_str());
#endif
s = utils::path::finalize("/cow///moose/////dog/chicken");
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\cow\moose\dog\chicken)").c_str(), s.c_str());
#else
EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str());
#endif
s = utils::path::finalize("\\cow\\\\\\moose\\\\\\\\dog\\chicken/");
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\cow\moose\dog\chicken)").c_str(), s.c_str());
#else
EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str());
#endif
s = utils::path::finalize("/cow\\\\/moose\\\\/\\dog\\chicken\\");
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\cow\moose\dog\chicken)").c_str(), s.c_str());
#else
EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str());
#endif
#if defined(_WIN32)
s = utils::path::finalize("D:");
EXPECT_STREQ(test_path("d:").c_str(), s.c_str());
s = utils::path::finalize("D:\\");
EXPECT_STREQ(test_path("d:").c_str(), s.c_str());
s = utils::path::finalize("D:\\moose");
EXPECT_STREQ(test_path("d:\\moose").c_str(), s.c_str());
s = utils::path::finalize("D:\\moose\\");
EXPECT_STREQ(test_path("d:\\moose").c_str(), s.c_str());
s = utils::path::finalize("D:/");
EXPECT_STREQ(test_path("d:").c_str(), s.c_str());
s = utils::path::finalize("D:/moose");
EXPECT_STREQ(test_path("d:\\moose").c_str(), s.c_str());
s = utils::path::finalize("D:/moose/");
EXPECT_STREQ(test_path("d:\\moose").c_str(), s.c_str());
s = utils::path::finalize("\\\\moose\\cow");
EXPECT_STREQ("\\\\moose\\cow", s.c_str());
s = utils::path::finalize("//moose/cow");
EXPECT_STREQ("\\\\moose\\cow", s.c_str());
#else // !defined(_WIN32)
s = utils::path::finalize("\\\\moose\\cow");
EXPECT_STREQ("/moose/cow", s.c_str());
s = utils::path::finalize("//moose/cow");
EXPECT_STREQ("/moose/cow", s.c_str());
#endif // defined(_WIN32)
}
TEST(utils_path, absolute) {
auto dir = utils::path::get_current_path<std::string>();
auto path = utils::path::absolute(".");
EXPECT_STREQ(dir.c_str(), path.c_str());
path = utils::path::absolute("./");
EXPECT_STREQ(dir.c_str(), path.c_str());
path = utils::path::absolute(R"(.\)");
EXPECT_STREQ(dir.c_str(), path.c_str());
#if defined(_WIN32)
path = utils::path::absolute(R"(.\moose)");
EXPECT_STREQ((dir + R"(\moose)").c_str(), path.c_str());
path = utils::path::absolute(R"(./moose)");
EXPECT_STREQ((dir + R"(\moose)").c_str(), path.c_str());
path = utils::path::absolute(R"(\\server\share)");
EXPECT_STREQ(R"(\\server\share)", path.c_str());
path = utils::path::absolute(R"(//server/share)");
EXPECT_STREQ(R"(\\server\share)", path.c_str());
auto home_env = utils::get_environment_variable("USERPROFILE");
#else // !defined(_WIN32)
path = utils::path::absolute(R"(.\moose)");
EXPECT_STREQ((dir + R"(/moose)").c_str(), path.c_str());
path = utils::path::absolute(R"(./moose)");
EXPECT_STREQ((dir + R"(/moose)").c_str(), path.c_str());
path = utils::path::absolute(R"(\\server\share)");
EXPECT_STREQ(R"(/server/share)", path.c_str());
#endif // defined(_WIN32)
}
TEST(utils_path, absolute_can_resolve_path_variables) {
#if defined(_WIN32)
auto home =
utils::path::absolute(utils::get_environment_variable("USERPROFILE"));
EXPECT_STREQ(home.c_str(), utils::path::absolute("%USERPROFILE%").c_str());
#else // !defined(_WIN32)
auto home = utils::path::absolute(utils::get_environment_variable("HOME"));
#endif // defined(_WIN32)
auto expanded_str = utils::path::absolute("~\\");
EXPECT_STREQ(home.c_str(), expanded_str.c_str());
expanded_str = utils::path::absolute("~/");
EXPECT_STREQ(home.c_str(), expanded_str.c_str());
expanded_str = utils::path::absolute("~");
EXPECT_STREQ("~", expanded_str.c_str());
}
TEST(utils_path, get_parent_path) {
#if defined(_WIN32)
{
auto dir = R"(c:\test)";
auto parent = utils::path::get_parent_path(dir);
EXPECT_STREQ("c:", parent.c_str());
dir = R"(c:\test\file.txt)";
parent = utils::path::get_parent_path(dir);
EXPECT_STREQ(R"(c:\test)", parent.c_str());
dir = "c:";
parent = utils::path::get_parent_path(dir);
EXPECT_STREQ("c:", parent.c_str());
}
{
auto dir = LR"(c:\test)";
auto parent = utils::path::get_parent_path(dir);
EXPECT_STREQ(L"c:", parent.c_str());
dir = LR"(c:\test\file.txt)";
parent = utils::path::get_parent_path(dir);
EXPECT_STREQ(LR"(c:\test)", parent.c_str());
dir = L"c:";
parent = utils::path::get_parent_path(dir);
EXPECT_STREQ(L"c:", parent.c_str());
}
#else // !defined(_WIN32)
{
auto dir = "/test";
auto parent = utils::path::get_parent_path(dir);
EXPECT_STREQ("/", parent.c_str());
dir = "/test/test";
parent = utils::path::get_parent_path(dir);
EXPECT_STREQ("/test", parent.c_str());
}
{
auto dir = L"/test";
auto parent = utils::path::get_parent_path(dir);
EXPECT_STREQ(L"/", parent.c_str());
dir = L"/test/test";
parent = utils::path::get_parent_path(dir);
EXPECT_STREQ(L"/test", parent.c_str());
}
#endif // defined(_WIN32)
}
TEST(utils_path, contains_trash_directory) {
#if defined(_WIN32)
{
auto dir = R"(c:\$recycle.bin)";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
dir = R"(c:\$recycle.bin\moose.txt)";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
}
{
auto dir = LR"(c:\$recycle.bin)";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
dir = LR"(c:\$recycle.bin\moose.txt)";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
}
#else // !defined(_WIN32)
{
auto dir = "/$recycle.bin";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
dir = "/$recycle.bin/moose.txt";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
}
{
auto dir = L"/$recycle.bin";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
dir = L"/$recycle.bin/moose.txt";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
}
#endif // defined(_WIN32)
}
TEST(utils_path, does_not_contain_trash_directory) {
#if defined(_WIN32)
{
auto dir = R"(c:\recycle.bin)";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
dir = R"(c:\recycle.bin\moose.txt)";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
}
{
auto dir = LR"(c:\recycle.bin)";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
dir = LR"(c:\recycle.bin\moose.txt)";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
}
#else // !defined(_WIN32)
{
auto dir = "/recycle.bin";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
dir = "/recycle.bin/moose.txt)";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
}
{
auto dir = L"/recycle.bin";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
dir = L"/recycle.bin/moose.txt)";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
}
#endif // defined(_WIN32)
}
} // namespace monitarr

View File

@@ -0,0 +1,137 @@
/*
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 "test.hpp"
namespace monitarr {
TEST(utils_string, begins_with) {
std::string str{"moose"};
EXPECT_TRUE(utils::string::begins_with(str, "m"));
EXPECT_TRUE(utils::string::begins_with(str, "mo"));
EXPECT_TRUE(utils::string::begins_with(str, "moo"));
EXPECT_TRUE(utils::string::begins_with(str, "moos"));
EXPECT_TRUE(utils::string::begins_with(str, "moose"));
EXPECT_FALSE(utils::string::begins_with(str, "a"));
EXPECT_FALSE(utils::string::begins_with(str, "ma"));
EXPECT_FALSE(utils::string::begins_with(str, "moose1"));
std::wstring str_w{L"moose"};
EXPECT_TRUE(utils::string::begins_with(str_w, L"m"));
EXPECT_TRUE(utils::string::begins_with(str_w, L"mo"));
EXPECT_TRUE(utils::string::begins_with(str_w, L"moo"));
EXPECT_TRUE(utils::string::begins_with(str_w, L"moos"));
EXPECT_TRUE(utils::string::begins_with(str_w, L"moose"));
EXPECT_FALSE(utils::string::begins_with(str_w, L"a"));
EXPECT_FALSE(utils::string::begins_with(str_w, L"ma"));
EXPECT_FALSE(utils::string::begins_with(str_w, L"moose1"));
}
TEST(utils_string, contains) {
std::string str{R"(\\)"};
EXPECT_TRUE(utils::string::contains(str, "\\"));
EXPECT_FALSE(utils::string::contains(str, "/"));
std::wstring str_w{LR"(\\)"};
EXPECT_TRUE(utils::string::contains(str_w, L"\\"));
EXPECT_FALSE(utils::string::contains(str_w, L"/"));
}
TEST(utils_string, replace) {
std::string str{"moose"};
utils::string::replace(str, 'o', '0');
EXPECT_STREQ("m00se", str.c_str());
std::wstring str_w{L"moose"};
utils::string::replace(str_w, 'o', '0');
EXPECT_STREQ(L"m00se", str_w.c_str());
std::string str2{"\\\\\\"};
utils::string::replace(str2, '\\', '/');
EXPECT_STREQ("///", str2.c_str());
std::wstring str_w2{L"\\\\\\"};
utils::string::replace(str_w2, '\\', '/');
EXPECT_STREQ(L"///", str_w2.c_str());
std::string str3{"///"};
utils::string::replace(str3, '/', '\\');
EXPECT_STREQ("\\\\\\", str3.c_str());
std::wstring str_w3{L"///"};
utils::string::replace(str_w3, '/', '\\');
EXPECT_STREQ(L"\\\\\\", str_w3.c_str());
str.clear();
utils::string::replace(str, '/', '\\');
EXPECT_STREQ("", str.c_str());
str_w.clear();
utils::string::replace(str_w, '/', '\\');
EXPECT_STREQ(L"", str_w.c_str());
}
TEST(utils_string, replace_string) {
std::string str{"moose"};
utils::string::replace(str, "o", "0");
EXPECT_STREQ("m00se", str.c_str());
std::wstring str_w{L"moose"};
utils::string::replace(str_w, L"o", L"0");
EXPECT_STREQ(L"m00se", str_w.c_str());
}
TEST(utils_string, is_numeric) {
EXPECT_TRUE(utils::string::is_numeric("100"));
EXPECT_TRUE(utils::string::is_numeric("+100"));
EXPECT_TRUE(utils::string::is_numeric("-100"));
EXPECT_TRUE(utils::string::is_numeric("100.00"));
EXPECT_TRUE(utils::string::is_numeric("+100.00"));
EXPECT_TRUE(utils::string::is_numeric("-100.00"));
EXPECT_FALSE(utils::string::is_numeric("1.00.00"));
EXPECT_FALSE(utils::string::is_numeric("+1.00.00"));
EXPECT_FALSE(utils::string::is_numeric("-1.00.00"));
EXPECT_FALSE(utils::string::is_numeric("a1"));
EXPECT_FALSE(utils::string::is_numeric("1a"));
EXPECT_FALSE(utils::string::is_numeric("+"));
EXPECT_FALSE(utils::string::is_numeric("-"));
EXPECT_FALSE(utils::string::is_numeric(""));
}
TEST(utils_string, to_bool) {
EXPECT_TRUE(utils::string::to_bool("1"));
EXPECT_TRUE(utils::string::to_bool("-1"));
EXPECT_TRUE(utils::string::to_bool("0.1"));
EXPECT_TRUE(utils::string::to_bool("-0.1"));
EXPECT_TRUE(utils::string::to_bool("00000.1000000"));
EXPECT_TRUE(utils::string::to_bool("true"));
EXPECT_FALSE(utils::string::to_bool("false"));
EXPECT_FALSE(utils::string::to_bool("0"));
EXPECT_FALSE(utils::string::to_bool("00000.00000"));
}
} // namespace monitarr