This commit is contained in:
Mars 2025-05-01 19:47:01 -04:00
parent 9a95178095
commit 631964469f
Signed by: pupbrained
GPG key ID: 0FF5B8826803F895
26 changed files with 482 additions and 335 deletions

View file

@ -10,10 +10,10 @@
#include <toml++/impl/table.hpp> // toml::table #include <toml++/impl/table.hpp> // toml::table
#include <unistd.h> // getuid #include <unistd.h> // getuid
#include "src/core/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/core/util/helpers.hpp" #include "src/util/helpers.hpp"
#include "src/core/util/logging.hpp" #include "src/util/logging.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
namespace fs = std::filesystem; namespace fs = std::filesystem;

View file

@ -12,13 +12,13 @@
#include <pwd.h> // getpwuid, passwd #include <pwd.h> // getpwuid, passwd
#include <unistd.h> // getuid #include <unistd.h> // getuid
#include "src/core/util/helpers.hpp" #include "src/util/helpers.hpp"
#endif #endif
#include "src/core/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/core/util/error.hpp" #include "src/util/error.hpp"
#include "src/core/util/logging.hpp" #include "src/util/logging.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
#include "weather.hpp" #include "weather.hpp"

View file

@ -15,14 +15,14 @@
#include <glaze/json/read.hpp> // NOLINT(misc-include-cleaner) - glaze/json/read.hpp is needed for glz::read<glz::opts> #include <glaze/json/read.hpp> // NOLINT(misc-include-cleaner) - glaze/json/read.hpp is needed for glz::read<glz::opts>
#include <ios> // std::ios::{binary, trunc} #include <ios> // std::ios::{binary, trunc}
#include <iterator> // std::istreambuf_iterator #include <iterator> // std::istreambuf_iterator
#include <system_error> // std::error_code
#include <utility> // std::move #include <utility> // std::move
#include <variant> // std::{get, holds_alternative} #include <variant> // std::{get, holds_alternative}
#include "src/core/util/defs.hpp" #include "src/util/cache.hpp"
#include "src/core/util/error.hpp" #include "src/util/defs.hpp"
#include "src/core/util/logging.hpp" #include "src/util/error.hpp"
#include "src/core/util/types.hpp" #include "src/util/logging.hpp"
#include "src/util/types.hpp"
#include "config.hpp" #include "config.hpp"
@ -35,109 +35,10 @@ namespace {
using util::error::DracError, util::error::DracErrorCode; using util::error::DracError, util::error::DracErrorCode;
using util::types::usize, util::types::Err, util::types::Exception; using util::types::usize, util::types::Err, util::types::Exception;
using weather::Coords; using weather::Coords;
using namespace util::cache;
constexpr opts glaze_opts = { .error_on_unknown_keys = false }; constexpr opts glaze_opts = { .error_on_unknown_keys = false };
fn GetCachePath() -> Result<fs::path, String> {
std::error_code errc;
fs::path cachePath = fs::temp_directory_path(errc);
if (errc)
return Err("Failed to get temp directory: " + errc.message());
cachePath /= "weather_cache.beve";
return cachePath;
}
fn ReadCacheFromFile() -> Result<Output, String> {
Result<fs::path, String> cachePath = GetCachePath();
if (!cachePath)
return Err(cachePath.error());
std::ifstream ifs(*cachePath, std::ios::binary);
if (!ifs.is_open())
return Err("Cache file not found: " + cachePath->string());
debug_log("Reading from cache file...");
try {
const String content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
ifs.close();
if (content.empty())
return Err(std::format("BEVE cache file is empty: {}", cachePath->string()));
Output result;
if (const error_ctx glazeErr = read_beve(result, content); glazeErr.ec != error_code::none)
return Err(std::format(
"BEVE parse error reading cache (code {}): {}", static_cast<int>(glazeErr.ec), cachePath->string()
));
debug_log("Successfully read from cache file.");
return result;
} catch (const Exception& e) { return Err(std::format("Error reading cache: {}", e.what())); }
}
fn WriteCacheToFile(const Output& data) -> Result<void, String> {
using util::types::isize;
Result<fs::path, String> cachePath = GetCachePath();
if (!cachePath)
return Err(cachePath.error());
debug_log("Writing to cache file...");
fs::path tempPath = *cachePath;
tempPath += ".tmp";
try {
String binaryBuffer;
if (const error_ctx glazeErr = write_beve(data, binaryBuffer); glazeErr)
return Err(std::format("BEVE serialization error writing cache (code {})", static_cast<int>(glazeErr.ec)));
{
std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc);
if (!ofs.is_open())
return Err("Failed to open temp file: " + tempPath.string());
ofs.write(binaryBuffer.data(), static_cast<isize>(binaryBuffer.size()));
if (!ofs) {
std::error_code removeEc;
fs::remove(tempPath, removeEc);
return Err("Failed to write to temp BEVE cache file");
}
}
std::error_code errc;
fs::rename(tempPath, *cachePath, errc);
if (errc) {
if (!fs::remove(tempPath, errc))
debug_log("Failed to remove temp file: {}", errc.message());
return Err(std::format("Failed to replace cache file: {}", errc.message()));
}
debug_log("Successfully wrote to cache file.");
return {};
} catch (const std::ios_base::failure& e) {
std::error_code removeEc;
fs::remove(tempPath, removeEc);
return Err(std::format("Filesystem error writing BEVE cache file {}: {}", tempPath.string(), e.what()));
} catch (const Exception& e) {
std::error_code removeEc;
fs::remove(tempPath, removeEc);
return Err(std::format("File operation error during BEVE cache write: {}", e.what()));
} catch (...) {
std::error_code removeEc;
fs::remove(tempPath, removeEc);
return Err(std::format("Unknown error writing BEVE cache file: {}", tempPath.string()));
}
}
fn WriteCallback(void* contents, const usize size, const usize nmemb, String* str) -> usize { fn WriteCallback(void* contents, const usize size, const usize nmemb, String* str) -> usize {
const usize totalSize = size * nmemb; const usize totalSize = size * nmemb;
str->append(static_cast<char*>(contents), totalSize); str->append(static_cast<char*>(contents), totalSize);
@ -177,7 +78,7 @@ fn Weather::getWeatherInfo() const -> Result<Output, DracError> {
using namespace std::chrono; using namespace std::chrono;
using util::types::i32; using util::types::i32;
if (Result<Output, String> data = ReadCacheFromFile()) { if (Result<Output, DracError> data = ReadCache<Output>("weather")) {
const Output& dataVal = *data; const Output& dataVal = *data;
if (const duration<double> cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt)); if (const duration<double> cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt));
@ -188,15 +89,15 @@ fn Weather::getWeatherInfo() const -> Result<Output, DracError> {
debug_log("Cache expired"); debug_log("Cache expired");
} else { } else {
debug_log("Cache error: {}", data.error()); error_at(data.error());
} }
fn handleApiResult = [](const Result<Output, String>& result) -> Result<Output, DracError> { fn handleApiResult = [](const Result<Output, String>& result) -> Result<Output, DracError> {
if (!result) if (!result)
return Err(DracError(DracErrorCode::ApiUnavailable, result.error())); return Err(DracError(DracErrorCode::ApiUnavailable, result.error()));
if (Result<void, String> writeResult = WriteCacheToFile(*result); !writeResult) if (Result<void, DracError> writeResult = WriteCache("weather", *result); !writeResult)
error_log("Failed to write cache: {}", writeResult.error()); error_at(writeResult.error());
return *result; return *result;
}; };

View file

@ -3,7 +3,7 @@
#include <glaze/core/common.hpp> // object #include <glaze/core/common.hpp> // object
#include <glaze/core/meta.hpp> // Object #include <glaze/core/meta.hpp> // Object
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
namespace weather { namespace weather {
using glz::detail::Object, glz::object; using glz::detail::Object, glz::object;

91
src/core/package.hpp Normal file
View file

@ -0,0 +1,91 @@
#pragma once
#include <filesystem> // std::filesystem::path
#include <future> // std::future
#include <glaze/core/common.hpp> // glz::object
#include <glaze/core/meta.hpp> // glz::detail::Object
#include <vector> // std::vector
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/types.hpp"
namespace packages {
namespace fs = std::filesystem;
using util::error::DracError;
using util::types::Result, util::types::u64, util::types::i64, util::types::String, util::types::Vec,
util::types::Future;
/**
* @struct PkgCountCacheData
* @brief Structure for caching package count results along with a timestamp.
*
* Used to avoid redundant lookups in package manager databases or directories
* if the underlying data source hasn't changed recently.
*/
struct PkgCountCacheData {
u64 count {}; ///< The cached package count.
i64 timestampEpochSeconds {}; ///< The UNIX timestamp (seconds since epoch) when the count was cached.
// NOLINTBEGIN(readability-identifier-naming)
struct [[maybe_unused]] glaze {
using T = PkgCountCacheData;
static constexpr glz::detail::Object value =
glz::object("count", &T::count, "timestamp", &T::timestampEpochSeconds);
};
// NOLINTEND(readability-identifier-naming)
};
/**
* @struct PackageManagerInfo
* @brief Holds information needed to query a database-backed package manager.
*/
struct PackageManagerInfo {
String id; ///< Unique identifier for the package manager (used for cache key).
fs::path dbPath; ///< Filesystem path to the package manager's database.
String countQuery; ///< SQL query string to count the packages.
};
/**
* @brief Gets the total package count by querying all relevant package managers for the current OS.
* @details This function orchestrates the process:
* 1. Determines the set of relevant package managers (platform-specific + shared).
* 2. Launches asynchronous tasks to query each manager.
* 3. Aggregates the results, summing counts and logging errors.
* @return Result containing the total package count (u64) on success,
* or a DracError if the aggregation fails (though individual manager errors are logged).
*/
fn GetTotalCount() -> Result<u64, DracError>;
fn GetCountFromDb(const PackageManagerInfo& pmInfo) -> Result<u64, DracError>;
fn GetCountFromDirectory(
const String& pmId,
const fs::path& dirPath,
const String& fileExtensionFilter = "",
bool subtractOne = false
) -> Result<u64, DracError>;
#ifdef __linux__
fn GetDpkgCount() -> Result<u64, DracError>;
fn GetPacmanCount() -> Result<u64, DracError>;
fn GetMossCount() -> Result<u64, DracError>;
fn GetRpmCount() -> Result<u64, DracError>;
fn GetZypperCount() -> Result<u64, DracError>;
fn GetPortageCount() -> Result<u64, DracError>;
fn GetApkCount() -> Result<u64, DracError>;
#elif defined(__APPLE__)
fn GetHomebrewCount() -> Result<u64, DracError>;
fn GetMacPortsCount() -> Result<u64, DracError>;
#elif defined(_WIN32)
fn GetWinRTCount() -> Result<u64, DracError>;
fn GetChocolateyCount() -> Result<u64, DracError>;
fn GetScoopCount() -> Result<u64, DracError>;
#endif
#ifndef _WIN32
fn GetNixCount() -> Result<u64, DracError>;
#endif
fn GetCargoCount() -> Result<u64, DracError>;
} // namespace packages

View file

@ -7,10 +7,10 @@
#include "src/config/config.hpp" #include "src/config/config.hpp"
#include "src/config/weather.hpp" #include "src/config/weather.hpp"
#include "src/core/util/defs.hpp"
#include "src/core/util/error.hpp"
#include "src/core/util/types.hpp"
#include "src/os/os.hpp" #include "src/os/os.hpp"
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/types.hpp"
using util::error::DracError, util::error::DracErrorCode; using util::error::DracError, util::error::DracErrorCode;

View file

@ -4,9 +4,8 @@
#include "src/config/config.hpp" // Config #include "src/config/config.hpp" // Config
#include "src/config/weather.hpp" // weather::Output #include "src/config/weather.hpp" // weather::Output
#include "src/util/defs.hpp"
#include "util/defs.hpp" #include "src/util/types.hpp"
#include "util/types.hpp"
struct Config; struct Config;

View file

@ -10,9 +10,9 @@
#include "src/config/config.hpp" #include "src/config/config.hpp"
#include "src/config/weather.hpp" #include "src/config/weather.hpp"
#include "src/core/system_data.hpp" #include "src/core/system_data.hpp"
#include "src/core/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/core/util/logging.hpp" #include "src/util/logging.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
namespace ui { namespace ui {
using ftxui::Color; using ftxui::Color;

View file

@ -18,11 +18,11 @@
#include <unistd.h> // readlink #include <unistd.h> // readlink
#include <utility> // std::move #include <utility> // std::move
#include "src/core/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/core/util/error.hpp" #include "src/util/error.hpp"
#include "src/core/util/helpers.hpp" #include "src/util/helpers.hpp"
#include "src/core/util/logging.hpp" #include "src/util/logging.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
#include "src/wrappers/dbus.hpp" #include "src/wrappers/dbus.hpp"
#include "src/wrappers/wayland.hpp" #include "src/wrappers/wayland.hpp"
#include "src/wrappers/xcb.hpp" #include "src/wrappers/xcb.hpp"

View file

@ -15,13 +15,13 @@
#include <iterator> // std::istreambuf_iterator #include <iterator> // std::istreambuf_iterator
#include <glaze/beve/read.hpp> // glz::read_beve #include <glaze/beve/read.hpp> // glz::read_beve
#include <glaze/beve/write.hpp> // glz::write_beve #include <glaze/beve/write.hpp> // glz::write_beve
#include <glaze/core/context.hpp> // glz::{context, error_code, error_ctx}
#include <system_error> // std::error_code #include <system_error> // std::error_code
#include "src/core/util/defs.hpp" #include "src/util/cache.hpp"
#include "src/core/util/error.hpp" #include "src/util/defs.hpp"
#include "src/core/util/logging.hpp" #include "src/util/error.hpp"
#include "src/core/util/types.hpp" #include "src/util/logging.hpp"
#include "src/util/types.hpp"
// clang-format on // clang-format on
using util::error::DracError, util::error::DracErrorCode; using util::error::DracError, util::error::DracErrorCode;
@ -31,6 +31,7 @@ using util::types::u64, util::types::i64, util::types::Result, util::types::Err,
namespace { namespace {
namespace fs = std::filesystem; namespace fs = std::filesystem;
using namespace std::chrono; using namespace std::chrono;
using namespace util::cache;
using os::linux::PkgCountCacheData, os::linux::PackageManagerInfo; using os::linux::PkgCountCacheData, os::linux::PackageManagerInfo;
fn GetPackageCountInternalDir( fn GetPackageCountInternalDir(
@ -111,6 +112,68 @@ namespace {
return count; return count;
} }
fn GetPackageCountInternalDb(const PackageManagerInfo& pmInfo) -> Result<u64, DracError> {
const auto& [pmId, dbPath, countQuery] = pmInfo;
if (Result<PkgCountCacheData, DracError> cachedDataResult = ReadCache<PkgCountCacheData>(pmId)) {
const auto& [count, timestamp] = *cachedDataResult;
std::error_code errc;
const std::filesystem::file_time_type dbModTime = fs::last_write_time(dbPath, errc);
if (errc) {
warn_log(
"Could not get modification time for '{}': {}. Invalidating {} cache.", dbPath.string(), errc.message(), pmId
);
} else {
if (const system_clock::time_point cacheTimePoint = system_clock::time_point(seconds(timestamp));
cacheTimePoint.time_since_epoch() >= dbModTime.time_since_epoch()) {
debug_log(
"Using valid {} package count cache (DB file unchanged since {}).",
pmId,
std::format("{:%F %T %Z}", floor<seconds>(cacheTimePoint))
);
return count;
}
debug_log("{} package count cache stale (DB file modified).", pmId);
}
} else {
if (cachedDataResult.error().code != DracErrorCode::NotFound)
debug_at(cachedDataResult.error());
debug_log("{} package count cache not found or unreadable.", pmId);
}
debug_log("Fetching fresh {} package count from database: {}", pmId, dbPath.string());
u64 count = 0;
try {
const SQLite::Database database(dbPath.string(), SQLite::OPEN_READONLY);
if (SQLite::Statement query(database, countQuery); query.executeStep()) {
const i64 countInt64 = query.getColumn(0).getInt64();
if (countInt64 < 0)
return Err(
DracError(DracErrorCode::ParseError, std::format("Negative count returned by {} DB COUNT query.", pmId))
);
count = static_cast<u64>(countInt64);
} else {
return Err(DracError(DracErrorCode::ParseError, std::format("No rows returned by {} DB COUNT query.", pmId)));
}
} catch (const SQLite::Exception& e) {
return Err(DracError(
DracErrorCode::ApiUnavailable, std::format("SQLite error occurred accessing {} DB: {}", pmId, e.what())
));
} catch (const Exception& e) { return Err(DracError(DracErrorCode::InternalError, e.what())); } catch (...) {
return Err(DracError(DracErrorCode::Other, std::format("Unknown error occurred accessing {} DB", pmId)));
}
const i64 nowEpochSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
const PkgCountCacheData dataToCache = { .count = count, .timestampEpochSeconds = nowEpochSeconds };
if (Result<void, DracError> writeResult = WriteCache(pmId, dataToCache); !writeResult)
error_at(writeResult.error());
return count;
}
} // namespace } // namespace
namespace os::linux { namespace os::linux {
@ -159,10 +222,9 @@ namespace os::linux {
fn GetTotalPackageCount() -> Result<u64, DracError> { fn GetTotalPackageCount() -> Result<u64, DracError> {
using util::types::Array, util::types::Future; using util::types::Array, util::types::Future;
Array<Future<Result<u64, DracError>>, 4> futures = { Array<Future<Result<u64, DracError>>, 3> futures = {
std::async(std::launch::async, GetDpkgPackageCount), std::async(std::launch::async, GetDpkgPackageCount),
std::async(std::launch::async, GetMossPackageCount), std::async(std::launch::async, GetMossPackageCount),
std::async(std::launch::async, GetNixPackageCount),
std::async(std::launch::async, GetPacmanPackageCount), std::async(std::launch::async, GetPacmanPackageCount),
}; };

View file

@ -7,9 +7,9 @@
#include <glaze/core/common.hpp> // glz::object #include <glaze/core/common.hpp> // glz::object
#include <glaze/core/meta.hpp> // glz::detail::Object #include <glaze/core/meta.hpp> // glz::detail::Object
#include "src/core/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/core/util/error.hpp" #include "src/util/error.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
// clang-format on // clang-format on
namespace os::linux { namespace os::linux {

View file

@ -7,9 +7,9 @@
#include "macos/bridge.hpp" #include "macos/bridge.hpp"
#include "os.hpp" #include "os.hpp"
#include "src/core/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/core/util/error.hpp" #include "src/util/error.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
// clang-format on // clang-format on
using namespace util::types; using namespace util::types;

View file

@ -3,9 +3,9 @@
#ifdef __APPLE__ #ifdef __APPLE__
// clang-format off // clang-format off
#include "src/core/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/core/util/error.hpp" #include "src/util/error.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
// clang-format on // clang-format on
using util::error::DracError; using util::error::DracError;
using util::types::MediaInfo, util::types::String, util::types::Result; using util::types::MediaInfo, util::types::String, util::types::Result;

View file

@ -11,7 +11,7 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include "src/core/util/error.hpp" #include "src/util/error.hpp"
// clang-format on // clang-format on
using util::error::DracErrorCode; using util::error::DracErrorCode;

View file

@ -1,8 +1,8 @@
#pragma once #pragma once
#include "src/core/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/core/util/error.hpp" #include "src/util/error.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
/** /**
* @namespace os * @namespace os

View file

@ -8,17 +8,17 @@
#include <glaze/beve/read.hpp> // glz::read_beve #include <glaze/beve/read.hpp> // glz::read_beve
#include <glaze/beve/write.hpp> // glz::write_beve #include <glaze/beve/write.hpp> // glz::write_beve
#include <glaze/core/common.hpp> // glz::object #include <glaze/core/common.hpp> // glz::object
#include <glaze/core/context.hpp> // glz::{context, error_code, error_ctx}
#include <glaze/core/meta.hpp> // glz::detail::Object #include <glaze/core/meta.hpp> // glz::detail::Object
#include <ios> // std::ios::{binary, trunc}, std::ios_base #include <ios> // std::ios::{binary, trunc}, std::ios_base
#include <iterator> // std::istreambuf_iterator #include <iterator> // std::istreambuf_iterator
#include <system_error> // std::error_code #include <system_error> // std::error_code
#include "src/core/util/defs.hpp" #include "src/util/cache.hpp"
#include "src/core/util/error.hpp" #include "src/util/defs.hpp"
#include "src/core/util/helpers.hpp" #include "src/util/error.hpp"
#include "src/core/util/logging.hpp" #include "src/util/helpers.hpp"
#include "src/core/util/types.hpp" #include "src/util/logging.hpp"
#include "src/util/types.hpp"
#include "os.hpp" #include "os.hpp"
@ -30,6 +30,7 @@ namespace fs = std::filesystem;
namespace { namespace {
using namespace std::chrono; using namespace std::chrono;
using namespace util::cache;
struct PackageManagerInfo { struct PackageManagerInfo {
String id; String id;
@ -51,136 +52,10 @@ namespace {
// NOLINTEND(readability-identifier-naming) // NOLINTEND(readability-identifier-naming)
}; };
constexpr StringView ALLOWED_PMID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
fn GetPkgCountCachePath(const String& pmId) -> Result<fs::path, DracError> {
std::error_code errc;
const fs::path cacheDir = fs::temp_directory_path(errc);
if (errc)
return Err(DracError(DracErrorCode::IoError, "Failed to get temp directory: " + errc.message()));
if (pmId.empty() || pmId.find_first_not_of(ALLOWED_PMID_CHARS) != String::npos)
return Err(DracError(DracErrorCode::ParseError, "Invalid package manager ID for cache path: " + pmId));
return cacheDir / (pmId + "_pkg_count_cache.beve");
}
fn ReadPkgCountCache(const String& pmId) -> Result<PkgCountCacheData, DracError> {
Result<fs::path, DracError> cachePathResult = GetPkgCountCachePath(pmId);
if (!cachePathResult)
return Err(cachePathResult.error());
const fs::path& cachePath = *cachePathResult;
if (!fs::exists(cachePath))
return Err(DracError(DracErrorCode::NotFound, "Cache file not found: " + cachePath.string()));
std::ifstream ifs(cachePath, std::ios::binary);
if (!ifs.is_open())
return Err(DracError(DracErrorCode::IoError, "Failed to open cache file for reading: " + cachePath.string()));
try {
const String content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
ifs.close();
if (content.empty())
return Err(DracError(DracErrorCode::ParseError, "BEVE cache file is empty: " + cachePath.string()));
PkgCountCacheData result;
const glz::context ctx {};
if (glz::error_ctx glazeResult = glz::read_beve(result, content); glazeResult.ec != glz::error_code::none)
return Err(DracError(
DracErrorCode::ParseError,
std::format(
"BEVE parse error reading cache (code {}): {}", static_cast<int>(glazeResult.ec), cachePath.string()
)
));
return result;
} catch (const std::ios_base::failure& e) {
return Err(DracError(
DracErrorCode::IoError, std::format("Filesystem error reading cache file {}: {}", cachePath.string(), e.what())
));
} catch (const Exception& e) {
return Err(DracError(DracErrorCode::InternalError, std::format("Error reading package count cache: {}", e.what()))
);
}
}
fn WritePkgCountCache(const String& pmId, const PkgCountCacheData& data) -> Result<void, DracError> {
using util::types::isize;
Result<fs::path, DracError> cachePathResult = GetPkgCountCachePath(pmId);
if (!cachePathResult)
return Err(cachePathResult.error());
const fs::path& cachePath = *cachePathResult;
fs::path tempPath = cachePath;
tempPath += ".tmp";
try {
String binaryBuffer;
PkgCountCacheData mutableData = data;
if (glz::error_ctx glazeErr = glz::write_beve(mutableData, binaryBuffer); glazeErr)
return Err(DracError(
DracErrorCode::ParseError,
std::format("BEVE serialization error writing cache (code {})", static_cast<int>(glazeErr.ec))
));
{
std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc);
if (!ofs.is_open())
return Err(DracError(DracErrorCode::IoError, "Failed to open temp cache file: " + tempPath.string()));
ofs.write(binaryBuffer.data(), static_cast<isize>(binaryBuffer.size()));
if (!ofs) {
std::error_code removeEc;
fs::remove(tempPath, removeEc);
return Err(DracError(DracErrorCode::IoError, "Failed to write to temp cache file: " + tempPath.string()));
}
}
std::error_code errc;
fs::rename(tempPath, cachePath, errc);
if (errc) {
fs::remove(tempPath, errc);
return Err(DracError(
DracErrorCode::IoError,
std::format("Failed to replace cache file '{}': {}", cachePath.string(), errc.message())
));
}
return {};
} catch (const std::ios_base::failure& e) {
std::error_code removeEc;
fs::remove(tempPath, removeEc);
return Err(DracError(
DracErrorCode::IoError, std::format("Filesystem error writing cache file {}: {}", tempPath.string(), e.what())
));
} catch (const Exception& e) {
std::error_code removeEc;
fs::remove(tempPath, removeEc);
return Err(DracError(DracErrorCode::InternalError, std::format("Error writing package count cache: {}", e.what()))
);
} catch (...) {
std::error_code removeEc;
fs::remove(tempPath, removeEc);
return Err(DracError(DracErrorCode::Other, std::format("Unknown error writing cache file: {}", tempPath.string()))
);
}
}
fn GetPackageCountInternalDb(const PackageManagerInfo& pmInfo) -> Result<u64, DracError> { fn GetPackageCountInternalDb(const PackageManagerInfo& pmInfo) -> Result<u64, DracError> {
const auto& [pmId, dbPath, countQuery] = pmInfo; const auto& [pmId, dbPath, countQuery] = pmInfo;
if (Result<PkgCountCacheData, DracError> cachedDataResult = ReadPkgCountCache(pmId)) { if (Result<PkgCountCacheData, DracError> cachedDataResult = ReadCache<PkgCountCacheData>(pmId)) {
const auto& [count, timestamp] = *cachedDataResult; const auto& [count, timestamp] = *cachedDataResult;
std::error_code errc; std::error_code errc;
const std::filesystem::file_time_type dbModTime = fs::last_write_time(dbPath, errc); const std::filesystem::file_time_type dbModTime = fs::last_write_time(dbPath, errc);
@ -233,12 +108,13 @@ namespace {
const i64 nowEpochSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count(); const i64 nowEpochSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
const PkgCountCacheData dataToCache = { .count = count, .timestampEpochSeconds = nowEpochSeconds }; const PkgCountCacheData dataToCache = { .count = count, .timestampEpochSeconds = nowEpochSeconds };
if (Result<void, DracError> writeResult = WritePkgCountCache(pmId, dataToCache); !writeResult) if (Result<void, DracError> writeResult = WriteCache(pmId, dataToCache); !writeResult)
error_at(writeResult.error()); error_at(writeResult.error());
return count; return count;
} }
#ifndef _WIN32
fn GetNixPackageCount() -> Result<u64, DracError> { fn GetNixPackageCount() -> Result<u64, DracError> {
debug_log("Attempting to get Nix package count."); debug_log("Attempting to get Nix package count.");
@ -259,6 +135,7 @@ namespace {
return GetPackageCountInternalDb(nixInfo); return GetPackageCountInternalDb(nixInfo);
} }
#endif
fn GetCargoPackageCount() -> Result<u64, DracError> { fn GetCargoPackageCount() -> Result<u64, DracError> {
using util::helpers::GetEnv; using util::helpers::GetEnv;
@ -288,12 +165,15 @@ namespace {
namespace os::shared { namespace os::shared {
fn GetPackageCount() -> Result<u64, DracError> { fn GetPackageCount() -> Result<u64, DracError> {
u64 count = 0; u64 count = 0;
if (Result<u64, DracError> pkgCount = GetNixPackageCount())
#ifndef _WIN32
if (const Result<u64, DracError> pkgCount = GetNixPackageCount())
count += *pkgCount; count += *pkgCount;
else else
debug_at(pkgCount.error()); debug_at(pkgCount.error());
#endif
if (Result<u64, DracError> pkgCount = GetCargoPackageCount()) if (const Result<u64, DracError> pkgCount = GetCargoPackageCount())
count += *pkgCount; count += *pkgCount;
else else
debug_at(pkgCount.error()); debug_at(pkgCount.error());

View file

@ -16,10 +16,10 @@
#include <winrt/base.h> #include <winrt/base.h>
#include <winrt/impl/Windows.Media.Control.2.h> #include <winrt/impl/Windows.Media.Control.2.h>
#include "src/core/util/error.hpp" #include "src/util/error.hpp"
#include "src/core/util/helpers.hpp" #include "src/util/helpers.hpp"
#include "src/core/util/logging.hpp" #include "src/util/logging.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
#include "os.hpp" #include "os.hpp"
// clang-format on // clang-format on

216
src/util/cache.hpp Normal file
View file

@ -0,0 +1,216 @@
#pragma once
#include <filesystem> // std::filesystem
#include <fstream> // std::{ifstream, ofstream}
#include <glaze/beve/read.hpp> // glz::read_beve
#include <glaze/beve/write.hpp> // glz::write_beve
#include <glaze/core/context.hpp> // glz::{context, error_code, error_ctx}
#include <iterator> // std::istreambuf_iterator
#include <string> // std::string
#include <system_error> // std::error_code
#include <type_traits> // std::decay_t
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/logging.hpp"
#include "src/util/types.hpp"
namespace util::cache {
namespace fs = std::filesystem;
using error::DracError, error::DracErrorCode;
using types::Err, types::Exception, types::Result, types::String, types::isize;
/**
* @brief Gets the full path for a cache file based on a unique key.
* @param cache_key A unique identifier for the cache (e.g., "weather", "pkg_count_pacman").
* Should ideally only contain filesystem-safe characters.
* @return Result containing the filesystem path on success, or a DracError on failure.
*/
inline fn GetCachePath(const String& cache_key) -> Result<fs::path, DracError> {
if (cache_key.empty())
return Err(DracError(DracErrorCode::InvalidArgument, "Cache key cannot be empty."));
// Basic check for potentially problematic characters in the key for filename safety.
// You might want to expand this or implement more robust sanitization if needed.
if (cache_key.find_first_of("/\\:*?\"<>|") != String::npos)
return Err(
DracError(DracErrorCode::InvalidArgument, std::format("Cache key '{}' contains invalid characters.", cache_key))
);
std::error_code errc;
const fs::path cacheDir = fs::temp_directory_path(errc);
if (errc)
return Err(DracError(DracErrorCode::IoError, "Failed to get system temporary directory: " + errc.message()));
// Use a consistent naming scheme
return cacheDir / (cache_key + "_cache.beve");
}
/**
* @brief Reads and deserializes data from a BEVE cache file.
* @tparam T The type of the object to deserialize from the cache. Must be Glaze-compatible.
* @param cache_key The unique identifier for the cache.
* @return Result containing the deserialized object of type T on success, or a DracError on failure.
*/
template <typename T>
fn ReadCache(const String& cache_key) -> Result<T, DracError> {
Result<fs::path, DracError> cachePathResult = GetCachePath(cache_key);
if (!cachePathResult)
return Err(cachePathResult.error());
const fs::path& cachePath = *cachePathResult;
if (std::error_code exists_ec; !fs::exists(cachePath, exists_ec) || exists_ec) {
if (exists_ec) {
// Log if there was an error checking existence, but still return NotFound
warn_log("Error checking existence of cache file '{}': {}", cachePath.string(), exists_ec.message());
}
return Err(DracError(DracErrorCode::NotFound, "Cache file not found: " + cachePath.string()));
}
std::ifstream ifs(cachePath, std::ios::binary);
if (!ifs.is_open())
return Err(DracError(DracErrorCode::IoError, "Failed to open cache file for reading: " + cachePath.string()));
debug_log("Reading cache for key '{}' from: {}", cache_key, cachePath.string());
try {
// Read the entire file content
const String content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
ifs.close(); // Close the file as soon as content is read
if (content.empty())
return Err(DracError(DracErrorCode::ParseError, "BEVE cache file is empty: " + cachePath.string()));
// Ensure T is default constructible for Glaze
static_assert(std::is_default_constructible_v<T>, "Cache type T must be default constructible for Glaze.");
T result {};
if (glz::error_ctx glazeErr = glz::read_beve(result, content); glazeErr.ec != glz::error_code::none) {
return Err(DracError(
DracErrorCode::ParseError,
std::format(
"BEVE parse error reading cache '{}' (code {}): {}",
cachePath.string(),
static_cast<int>(glazeErr.ec),
glz::format_error(glazeErr, content)
)
));
}
debug_log("Successfully read cache for key '{}'.", cache_key);
return result;
} catch (const std::ios_base::failure& e) {
return Err(DracError(
DracErrorCode::IoError, std::format("Filesystem error reading cache file {}: {}", cachePath.string(), e.what())
));
} catch (const Exception& e) {
// Catching std::exception or a project-specific base exception
return Err(DracError(
DracErrorCode::InternalError,
std::format("Standard exception reading cache file {}: {}", cachePath.string(), e.what())
));
} catch (...) {
return Err(DracError(DracErrorCode::Other, "Unknown error reading cache file: " + cachePath.string()));
}
}
/**
* @brief Serializes and writes data to a BEVE cache file safely.
* @tparam T The type of the object to serialize. Must be Glaze-compatible.
* @param cache_key The unique identifier for the cache.
* @param data The data object of type T to write to the cache.
* @return Result containing void on success, or a DracError on failure.
*/
template <typename T>
fn WriteCache(const String& cache_key, const T& data) -> Result<void, DracError> {
Result<fs::path, DracError> cachePathResult = GetCachePath(cache_key);
if (!cachePathResult)
return Err(cachePathResult.error());
const fs::path& cachePath = *cachePathResult;
fs::path tempPath = cachePath;
tempPath += ".tmp"; // Use a temporary file for atomic write
debug_log("Writing cache for key '{}' to: {}", cache_key, cachePath.string());
try {
String binaryBuffer;
// Use Glaze to serialize
// Need to decay T in case it's a reference type from the caller
using DecayedT = std::decay_t<T>;
DecayedT dataToSerialize = data; // Make a copy if needed for non-const Glaze operations
if (glz::error_ctx glazeErr = glz::write_beve(dataToSerialize, binaryBuffer); glazeErr) {
return Err(DracError(
DracErrorCode::ParseError,
std::format(
"BEVE serialization error writing cache for key '{}' (code {}): {}",
cache_key,
static_cast<int>(glazeErr.ec),
glz::format_error(glazeErr, binaryBuffer)
)
));
}
// Scope for ofstream to ensure it's closed before rename
{
std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc);
if (!ofs.is_open())
return Err(DracError(DracErrorCode::IoError, "Failed to open temporary cache file: " + tempPath.string()));
ofs.write(binaryBuffer.data(), static_cast<isize>(binaryBuffer.size()));
if (!ofs) {
// Attempt cleanup before returning error
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Ignore error on cleanup attempt
return Err(DracError(DracErrorCode::IoError, "Failed to write to temporary cache file: " + tempPath.string())
);
}
// ofstream automatically closed here
}
// Attempt to atomically replace the old cache file
std::error_code rename_ec;
fs::rename(tempPath, cachePath, rename_ec);
if (rename_ec) {
// If rename failed, attempt to clean up the temp file
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Ignore error on cleanup attempt
return Err(DracError(
DracErrorCode::IoError,
std::format(
"Failed to replace cache file '{}' with temporary file '{}': {}",
cachePath.string(),
tempPath.string(),
rename_ec.message()
)
));
}
debug_log("Successfully wrote cache for key '{}'.", cache_key);
return {}; // Success
} catch (const std::ios_base::failure& e) {
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Cleanup attempt
return Err(DracError(
DracErrorCode::IoError, std::format("Filesystem error writing cache file {}: {}", tempPath.string(), e.what())
));
} catch (const Exception& e) {
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Cleanup attempt
return Err(DracError(
DracErrorCode::InternalError,
std::format("Standard exception writing cache file {}: {}", tempPath.string(), e.what())
));
} catch (...) {
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Cleanup attempt
return Err(DracError(DracErrorCode::Other, "Unknown error writing cache file: " + tempPath.string()));
}
}
} // namespace util::cache

View file

@ -9,7 +9,7 @@
#include <winrt/base.h> // winrt::hresult_error #include <winrt/base.h> // winrt::hresult_error
#endif #endif
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
namespace util::error { namespace util::error {
using types::u8, types::i32, types::String, types::StringView, types::Exception; using types::u8, types::i32, types::String, types::StringView, types::Exception;

View file

@ -1,8 +1,8 @@
#pragma once #pragma once
#include "defs.hpp" #include "src/util/defs.hpp"
#include "error.hpp" #include "src/util/error.hpp"
#include "types.hpp" #include "src/util/types.hpp"
namespace util::helpers { namespace util::helpers {
using error::DracError, error::DracErrorCode; using error::DracError, error::DracErrorCode;

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <chrono> // std::chrono::{days, floor, seconds, system_clock} #include <chrono> // std::chrono::{days, floor, seconds, system_clock}
#include <ctime> // For time_t, tm, localtime_s, localtime_r, strftime (needed for cross-platform local time)
#include <filesystem> // std::filesystem::path #include <filesystem> // std::filesystem::path
#include <format> // std::format #include <format> // std::format
#include <ftxui/screen/color.hpp> // ftxui::Color #include <ftxui/screen/color.hpp> // ftxui::Color
@ -12,9 +11,9 @@
#include <source_location> // std::source_location #include <source_location> // std::source_location
#endif #endif
#include "src/core/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/core/util/error.hpp" #include "src/util/error.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
namespace util::logging { namespace util::logging {
using types::usize, types::u8, types::i32, types::i64, types::CStr, types::String, types::StringView, types::Array, using types::usize, types::u8, types::i32, types::i64, types::CStr, types::String, types::StringView, types::Array,
@ -74,10 +73,8 @@ namespace util::logging {
* @param color The FTXUI color * @param color The FTXUI color
* @return Styled string with ANSI codes * @return Styled string with ANSI codes
*/ */
inline fn Colorize(StringView text, const ftxui::Color::Palette16& color) -> String { inline fn Colorize(const StringView text, const ftxui::Color::Palette16& color) -> String {
std::ostringstream oss; return std::format("{}{}{}", LogLevelConst::COLOR_CODE_LITERALS.at(color), text, LogLevelConst::RESET_CODE);
oss << LogLevelConst::COLOR_CODE_LITERALS.at(static_cast<i32>(color)) << text << LogLevelConst::RESET_CODE;
return oss.str();
} }
/** /**
@ -86,7 +83,7 @@ namespace util::logging {
* @return Bold text * @return Bold text
*/ */
inline fn Bold(const StringView text) -> String { inline fn Bold(const StringView text) -> String {
return String(LogLevelConst::BOLD_START) + String(text) + String(LogLevelConst::BOLD_END); return std::format("{}{}{}", LogLevelConst::BOLD_START, text, LogLevelConst::BOLD_END);
} }
/** /**
@ -95,7 +92,7 @@ namespace util::logging {
* @return Italic text * @return Italic text
*/ */
inline fn Italic(const StringView text) -> String { inline fn Italic(const StringView text) -> String {
return String(LogLevelConst::ITALIC_START) + String(text) + String(LogLevelConst::ITALIC_END); return std::format("{}{}{}", LogLevelConst::ITALIC_START, text, LogLevelConst::ITALIC_END);
} }
/** /**
@ -143,11 +140,14 @@ namespace util::logging {
} }
} }
// ReSharper disable once CppDoxygenUnresolvedReference
/** /**
* @brief Logs a message with the specified log level, source location, and format string. * @brief Logs a message with the specified log level, source location, and format string.
* @tparam Args Parameter pack for format arguments. * @tparam Args Parameter pack for format arguments.
* @param level The log level (DEBUG, INFO, WARN, ERROR). * @param level The log level (DEBUG, INFO, WARN, ERROR).
* \ifnot NDEBUG
* @param loc The source location of the log message (only in Debug builds). * @param loc The source location of the log message (only in Debug builds).
* \endif
* @param fmt The format string. * @param fmt The format string.
* @param args The arguments for the format string. * @param args The arguments for the format string.
*/ */
@ -163,8 +163,6 @@ namespace util::logging {
using namespace std::chrono; using namespace std::chrono;
using std::filesystem::path; using std::filesystem::path;
using Buffer = Array<char, 512>;
const auto nowTp = system_clock::now(); const auto nowTp = system_clock::now();
const std::time_t nowTt = system_clock::to_time_t(nowTp); const std::time_t nowTt = system_clock::to_time_t(nowTp);
std::tm localTm; std::tm localTm;
@ -176,7 +174,7 @@ namespace util::logging {
#else #else
if (localtime_r(&nowTt, &localTm) != nullptr) { if (localtime_r(&nowTt, &localTm) != nullptr) {
#endif #endif
Array<char, 64> timeBuffer; Array<char, 64> timeBuffer {};
if (std::strftime(timeBuffer.data(), sizeof(timeBuffer), LogLevelConst::TIMESTAMP_FORMAT, &localTm) > 0) if (std::strftime(timeBuffer.data(), sizeof(timeBuffer), LogLevelConst::TIMESTAMP_FORMAT, &localTm) > 0)
timestamp = timeBuffer.data(); timestamp = timeBuffer.data();
@ -187,7 +185,7 @@ namespace util::logging {
const String message = std::format(fmt, std::forward<Args>(args)...); const String message = std::format(fmt, std::forward<Args>(args)...);
Buffer buffer {}; Array<char, 128> buffer {};
// Use the locally formatted timestamp string here // Use the locally formatted timestamp string here
auto* iter = std::format_to( auto* iter = std::format_to(

View file

@ -9,9 +9,9 @@
#include <format> // std::format #include <format> // std::format
#include <type_traits> // std::is_convertible_v #include <type_traits> // std::is_convertible_v
#include "src/core/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/core/util/error.hpp" #include "src/util/error.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
// clang-format on // clang-format on
namespace dbus { namespace dbus {

View file

@ -5,8 +5,8 @@
// clang-format off // clang-format off
#include <wayland-client.h> // Wayland client library #include <wayland-client.h> // Wayland client library
#include "src/core/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
// clang-format on // clang-format on
struct wl_display; struct wl_display;

View file

@ -5,8 +5,8 @@
// clang-format off // clang-format off
#include <xcb/xcb.h> // XCB library #include <xcb/xcb.h> // XCB library
#include "src/core/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/core/util/types.hpp" #include "src/util/types.hpp"
// clang-format on // clang-format on
namespace xcb { namespace xcb {