From 0027fa65208b239218d13dbb285e7a79ab0c7e32 Mon Sep 17 00:00:00 2001 From: Mars Date: Mon, 5 May 2025 01:41:50 -0400 Subject: [PATCH] 24ms average on WINDOWS is wild --- src/config/config.cpp | 14 +-- src/config/config.hpp | 6 +- src/config/weather.cpp | 33 +++--- src/core/package.cpp | 223 +++++++++++++++++++++----------------- src/core/package.hpp | 51 +++++---- src/core/system_data.cpp | 27 +++-- src/core/system_data.hpp | 26 ++--- src/main.cpp | 83 ++++++++------ src/os/bsd.cpp | 56 +++++----- src/os/haiku.cpp | 22 ++-- src/os/linux.cpp | 62 +++++------ src/os/macos.cpp | 20 ++-- src/os/macos/bridge.mm | 8 +- src/os/os.hpp | 18 +-- src/os/serenity.cpp | 22 ++-- src/os/windows.cpp | 93 +++++++++++----- src/util/cache.hpp | 67 +++++------- src/util/error.hpp | 229 ++++++++++++++++++++++----------------- src/util/helpers.hpp | 2 +- src/util/logging.hpp | 63 +++++++---- src/util/types.hpp | 25 +---- src/wrappers/dbus.hpp | 6 +- 22 files changed, 627 insertions(+), 529 deletions(-) diff --git a/src/config/config.cpp b/src/config/config.cpp index dafe0f9..c60bbe5 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -52,21 +52,21 @@ location = "London" # Your city name Vec possiblePaths; #ifdef _WIN32 - if (Result result = GetEnv("LOCALAPPDATA")) + if (Result result = GetEnv("LOCALAPPDATA")) possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml"); - if (Result result = GetEnv("USERPROFILE")) { + if (Result result = GetEnv("USERPROFILE")) { possiblePaths.push_back(fs::path(*result) / ".config" / "draconis++" / "config.toml"); possiblePaths.push_back(fs::path(*result) / "AppData" / "Local" / "draconis++" / "config.toml"); } - if (Result result = GetEnv("APPDATA")) + if (Result result = GetEnv("APPDATA")) possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml"); #else - if (Result result = GetEnv("XDG_CONFIG_HOME")) + if (Result result = GetEnv("XDG_CONFIG_HOME")) possiblePaths.emplace_back(fs::path(*result) / "draconis++" / "config.toml"); - if (Result result = GetEnv("HOME")) { + if (Result result = GetEnv("HOME")) { possiblePaths.emplace_back(fs::path(*result) / ".config" / "draconis++" / "config.toml"); possiblePaths.emplace_back(fs::path(*result) / ".draconis++" / "config.toml"); } @@ -121,8 +121,8 @@ location = "London" # Your city name const passwd* pwd = getpwuid(getuid()); CStr pwdName = pwd ? pwd->pw_name : nullptr; - const Result envUser = util::helpers::GetEnv("USER"); - const Result envLogname = util::helpers::GetEnv("LOGNAME"); + const Result envUser = util::helpers::GetEnv("USER"); + const Result envLogname = util::helpers::GetEnv("LOGNAME"); defaultName = pwdName ? pwdName : envUser ? *envUser : envLogname ? *envLogname : "User"; #endif diff --git a/src/config/config.hpp b/src/config/config.hpp index 1e5f02d..16338e3 100644 --- a/src/config/config.hpp +++ b/src/config/config.hpp @@ -57,11 +57,11 @@ struct General { return pwd->pw_name; // Try to get the username using environment variables - if (Result envUser = GetEnv("USER")) + if (Result envUser = GetEnv("USER")) return *envUser; // Finally, try to get the username using LOGNAME - if (Result envLogname = GetEnv("LOGNAME")) + if (Result envLogname = GetEnv("LOGNAME")) return *envLogname; // If all else fails, return a default name @@ -151,7 +151,7 @@ struct Weather { * API key, and units. It returns a WeatherOutput object containing the * retrieved weather data. */ - [[nodiscard]] fn getWeatherInfo() const -> Result; + [[nodiscard]] fn getWeatherInfo() const -> Result; }; /** diff --git a/src/config/weather.cpp b/src/config/weather.cpp index 3e3df0a..2aca3ba 100644 --- a/src/config/weather.cpp +++ b/src/config/weather.cpp @@ -13,7 +13,6 @@ #include // glz::opts #include // glz::format_error #include // NOLINT(misc-include-cleaner) - glaze/json/read.hpp is needed for glz::read -#include // std::ios::{binary, trunc} #include // std::istreambuf_iterator #include // std::{get, holds_alternative} @@ -44,13 +43,13 @@ namespace { return totalSize; } - fn MakeApiRequest(const String& url) -> Result { + fn MakeApiRequest(const String& url) -> Result { debug_log("Making API request to URL: {}", url); CURL* curl = curl_easy_init(); String responseBuffer; if (!curl) - return Err("Failed to initialize cURL"); + return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to initialize cURL")); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); @@ -62,41 +61,40 @@ namespace { curl_easy_cleanup(curl); if (res != CURLE_OK) - return Err(std::format("cURL error: {}", curl_easy_strerror(res))); + return Err(DracError(DracErrorCode::ApiUnavailable, std::format("cURL error: {}", curl_easy_strerror(res)))); Output output; if (const error_ctx errc = read(output, responseBuffer); errc.ec != error_code::none) - return Err("API response parse error: " + format_error(errc, responseBuffer)); + return Err(DracError( + DracErrorCode::ParseError, std::format("Failed to parse JSON response: {}", format_error(errc, responseBuffer)) + )); return output; } } // namespace -fn Weather::getWeatherInfo() const -> Result { +fn Weather::getWeatherInfo() const -> Result { using namespace std::chrono; using util::types::i32; - if (Result data = ReadCache("weather")) { + if (Result data = ReadCache("weather")) { const Output& dataVal = *data; if (const duration cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt)); - cacheAge < 60min) { // NOLINT(misc-include-cleaner) - inherited from - debug_log("Using valid cache"); + cacheAge < 60min) // NOLINT(misc-include-cleaner) - inherited from return dataVal; - } debug_log("Cache expired"); - } else { + } else error_at(data.error()); - } - fn handleApiResult = [](const Result& result) -> Result { + fn handleApiResult = [](const Result& result) -> Result { if (!result) - return Err(DracError(DracErrorCode::ApiUnavailable, result.error())); + return Err(result.error()); - if (Result writeResult = WriteCache("weather", *result); !writeResult) - error_at(writeResult.error()); + if (Result writeResult = WriteCache("weather", *result); !writeResult) + return Err(writeResult.error()); return *result; }; @@ -106,8 +104,6 @@ fn Weather::getWeatherInfo() const -> Result { char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast(city.length())); - debug_log("Requesting city: {}", escaped); - const String apiUrl = std::format("https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units={}", escaped, apiKey, units); @@ -118,7 +114,6 @@ fn Weather::getWeatherInfo() const -> Result { if (std::holds_alternative(location)) { const auto& [lat, lon] = std::get(location); - debug_log("Requesting coordinates: lat={:.3f}, lon={:.3f}", lat, lon); const String apiUrl = std::format( "https://api.openweathermap.org/data/2.5/weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", lat, lon, apiKey, units diff --git a/src/core/package.cpp b/src/core/package.cpp index e7f1905..e626084 100644 --- a/src/core/package.cpp +++ b/src/core/package.cpp @@ -1,10 +1,10 @@ #include "package.hpp" -#ifndef __serenity__ +#if !defined(__serenity_) && !defined(_WIN32) #include // SQLite::{Database, OPEN_READONLY} #include // SQLite::Exception #include // SQLite::Statement -#endif // __serenity__ +#endif #include // std::chrono #include // std::filesystem @@ -23,93 +23,131 @@ using namespace std::chrono; using util::cache::ReadCache, util::cache::WriteCache; using util::error::DracError, util::error::DracErrorCode; using util::types::Err, util::types::Exception, util::types::Future, util::types::Result, util::types::String, - util::types::Vec, util::types::i64, util::types::u64; + util::types::Vec, util::types::i64, util::types::u64, util::types::Option, util::types::None; namespace { fn GetCountFromDirectoryImpl( - const String& pmId, - const fs::path& dirPath, - const String& fileExtensionFilter, - const bool subtractOne - ) -> Result { - debug_log("Counting packages for '{}' in directory: {}", pmId, dirPath.string()); + const String& pmId, + const fs::path& dirPath, + const Option& fileExtensionFilter, + const bool subtractOne + ) -> Result { + using package::PkgCountCacheData; - std::error_code errc; + std::error_code fsErrCode; - if (!fs::exists(dirPath, errc)) { - if (errc) - warn_log("Filesystem error checking {} directory '{}': {}", pmId, dirPath.string(), errc.message()); + if (Result cachedDataResult = ReadCache(pmId)) { + const auto& [cachedCount, timestamp] = *cachedDataResult; - return Err(DracError(DracErrorCode::NotFound, std::format("{} directory not found: {}", pmId, dirPath.string()))); - } + if (!fs::exists(dirPath, fsErrCode) || fsErrCode) + warn_log( + "Error checking existence for directory '{}' before cache validation: {}, Invalidating {} cache", + dirPath.string(), + fsErrCode.message(), + pmId + ); + else { + fsErrCode.clear(); + const fs::file_time_type dirModTime = fs::last_write_time(dirPath, fsErrCode); - errc.clear(); + if (fsErrCode) + warn_log( + "Could not get modification time for directory '{}': {}. Invalidating {} cache", + dirPath.string(), + fsErrCode.message(), + pmId + ); + else { + if (const system_clock::time_point cacheTimePoint = system_clock::time_point(seconds(timestamp)); + cacheTimePoint.time_since_epoch() >= dirModTime.time_since_epoch()) { + debug_log( + "Using valid {} directory count cache (Dir '{}' unchanged since {}). Count: {}", + pmId, + dirPath.string(), + std::format("{:%F %T %Z}", floor(cacheTimePoint)), + cachedCount + ); + return cachedCount; + } + } + } + } else if (cachedDataResult.error().code != DracErrorCode::NotFound) { + debug_at(cachedDataResult.error()); + } else + debug_log("{} directory count cache not found or unreadable", pmId, pmId); - if (!fs::is_directory(dirPath, errc)) { - if (errc) + fsErrCode.clear(); + + if (!fs::is_directory(dirPath, fsErrCode)) { + if (fsErrCode) return Err(DracError( DracErrorCode::IoError, - std::format("Filesystem error checking if '{}' is a directory: {}", dirPath.string(), errc.message()) + std::format("Filesystem error checking if '{}' is a directory: {}", dirPath.string(), fsErrCode.message()) )); - return Err( DracError(DracErrorCode::IoError, std::format("{} path is not a directory: {}", pmId, dirPath.string())) ); } + fsErrCode.clear(); - errc.clear(); - - u64 count = 0; - bool filterActive = !fileExtensionFilter.empty(); + u64 count = 0; try { - const fs::directory_iterator dirIter(dirPath, fs::directory_options::skip_permission_denied, errc); + const fs::directory_iterator dirIter(dirPath, fs::directory_options::skip_permission_denied, fsErrCode); - if (errc) { + if (fsErrCode) return Err(DracError( DracErrorCode::IoError, - std::format("Failed to create iterator for {} directory '{}': {}", pmId, dirPath.string(), errc.message()) + std::format( + "Failed to create iterator for {} directory '{}': {}", pmId, dirPath.string(), fsErrCode.message() + ) )); - } - - errc.clear(); for (const fs::directory_entry& entry : dirIter) { + fsErrCode.clear(); + if (entry.path().empty()) continue; - std::error_code entryStatErr; - bool isFile = false; + if (fileExtensionFilter) { + bool isFile = false; + isFile = entry.is_regular_file(fsErrCode); - if (filterActive) { - isFile = entry.is_regular_file(entryStatErr); - if (entryStatErr) { - warn_log( - "Error stating entry '{}' in {} directory: {}", entry.path().string(), pmId, entryStatErr.message() - ); - entryStatErr.clear(); + if (fsErrCode) { + warn_log("Error stating entry '{}' in {} directory: {}", entry.path().string(), pmId, fsErrCode.message()); continue; } + + if (isFile && entry.path().extension().string() == *fileExtensionFilter) + count++; + + continue; } - if (filterActive) { - if (isFile && entry.path().extension().string() == fileExtensionFilter) - count++; - } else + if (!fileExtensionFilter) count++; } - - } catch (const fs::filesystem_error& e) { - return Err(DracError(DracErrorCode::IoError, std::format("Filesystem error during {} directory iteration", pmId)) - ); - } catch (const Exception& e) { return Err(DracError(DracErrorCode::InternalError, e.what())); } catch (...) { + } catch (const fs::filesystem_error& fsCatchErr) { + return Err(DracError( + DracErrorCode::IoError, + std::format("Filesystem error during {} directory iteration: {}", pmId, fsCatchErr.what()) + )); + } catch (const Exception& exc) { return Err(DracError(DracErrorCode::InternalError, exc.what())); } catch (...) { return Err(DracError(DracErrorCode::Other, std::format("Unknown error iterating {} directory", pmId))); } if (subtractOne && count > 0) count--; - debug_log("Successfully counted {} packages for '{}': {}", std::to_string(count), pmId, dirPath.string()); + if (count == 0) + return Err(DracError(DracErrorCode::NotFound, std::format("No packages found in {} directory", pmId))); + + const i64 nowEpochSeconds = duration_cast(system_clock::now().time_since_epoch()).count(); + const PkgCountCacheData dataToCache = { .count = count, .timestampEpochSeconds = nowEpochSeconds }; + + if (Result writeResult = WriteCache(pmId, dataToCache); !writeResult) + error_at(writeResult.error()); + return count; } } // namespace @@ -120,32 +158,29 @@ namespace package { const fs::path& dirPath, const String& fileExtensionFilter, const bool subtractOne - ) -> Result { + ) -> Result { return GetCountFromDirectoryImpl(pmId, dirPath, fileExtensionFilter, subtractOne); } fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath, const String& fileExtensionFilter) - -> Result { + -> Result { return GetCountFromDirectoryImpl(pmId, dirPath, fileExtensionFilter, false); } - fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath, const bool subtractOne) - -> Result { - const String noFilter; - return GetCountFromDirectoryImpl(pmId, dirPath, noFilter, subtractOne); + fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath, const bool subtractOne) -> Result { + return GetCountFromDirectoryImpl(pmId, dirPath, None, subtractOne); } - fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath) -> Result { - const String noFilter; - return GetCountFromDirectoryImpl(pmId, dirPath, noFilter, false); + fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath) -> Result { + return GetCountFromDirectoryImpl(pmId, dirPath, None, false); } -#ifndef __serenity__ - fn GetCountFromDb(const PackageManagerInfo& pmInfo) -> Result { +#if !defined(__serenity__) && !defined(_WIN32) + fn GetCountFromDb(const PackageManagerInfo& pmInfo) -> Result { const auto& [pmId, dbPath, countQuery] = pmInfo; const String cacheKey = "pkg_count_" + pmId; // More specific cache key - if (Result cachedDataResult = ReadCache(cacheKey)) { + if (Result cachedDataResult = ReadCache(cacheKey)) { const auto& [count, timestamp] = *cachedDataResult; std::error_code errc; const std::filesystem::file_time_type dbModTime = fs::last_write_time(dbPath, errc); @@ -177,7 +212,6 @@ namespace package { u64 count = 0; try { - // Ensure database file exists before trying to open std::error_code existsErr; if (!fs::exists(dbPath, existsErr) || existsErr) { if (existsErr) { @@ -198,20 +232,13 @@ namespace package { DracError(DracErrorCode::ParseError, std::format("Negative count returned by {} DB COUNT query.", pmId)) ); count = static_cast(countInt64); - } else { - // It's possible a query legitimately returns 0 rows (e.g., no packages) - debug_log("No rows returned by {} DB COUNT query for '{}', assuming count is 0.", pmId, dbPath.string()); - count = 0; - // return Err(DracError(DracErrorCode::ParseError, std::format("No rows returned by {} DB COUNT query.", - // pmId))); - } + } else + return Err(DracError(DracErrorCode::ParseError, std::format("No rows returned by {} DB COUNT query.", pmId))); } catch (const SQLite::Exception& e) { - // Log specific SQLite errors but return a more general error type error_log("SQLite error occurred accessing {} DB '{}': {}", pmId, dbPath.string(), e.what()); - return Err(DracError( - DracErrorCode::ApiUnavailable, // Or IoError? - std::format("Failed to query {} database: {}", pmId, dbPath.string()) - )); + return Err( + DracError(DracErrorCode::ApiUnavailable, std::format("Failed to query {} database: {}", pmId, dbPath.string())) + ); } catch (const Exception& e) { error_log("Standard exception accessing {} DB '{}': {}", pmId, dbPath.string(), e.what()); return Err(DracError(DracErrorCode::InternalError, e.what())); @@ -225,15 +252,15 @@ namespace package { const i64 nowEpochSeconds = duration_cast(system_clock::now().time_since_epoch()).count(); const PkgCountCacheData dataToCache = { .count = count, .timestampEpochSeconds = nowEpochSeconds }; - if (Result writeResult = WriteCache(cacheKey, dataToCache); !writeResult) - error_at(writeResult.error()); // Log cache write error but return the count anyway + if (Result writeResult = WriteCache(cacheKey, dataToCache); !writeResult) + error_at(writeResult.error()); return count; } #endif // __serenity__ #if defined(__linux__) || defined(__APPLE__) - fn GetNixCount() -> Result { + fn GetNixCount() -> Result { const PackageManagerInfo nixInfo = { .id = "nix", .dbPath = "/nix/var/nix/db/db.sqlite", @@ -253,14 +280,14 @@ namespace package { } #endif // __linux__ || __APPLE__ - fn GetCargoCount() -> Result { + fn CountCargo() -> Result { using util::helpers::GetEnv; fs::path cargoPath {}; - if (const Result cargoHome = GetEnv("CARGO_HOME")) + if (const Result cargoHome = GetEnv("CARGO_HOME")) cargoPath = fs::path(*cargoHome) / "bin"; - else if (const Result homeDir = GetEnv("HOME")) + else if (const Result homeDir = GetEnv("HOME")) cargoPath = fs::path(*homeDir) / ".cargo" / "bin"; if (cargoPath.empty() || !fs::exists(cargoPath)) @@ -277,8 +304,8 @@ namespace package { return count; } - fn GetTotalCount() -> Result { - Vec>> futures; + fn GetTotalCount() -> Result { + Vec>> futures; #ifdef __linux__ futures.push_back(std::async(std::launch::async, GetDpkgCount)); @@ -292,9 +319,9 @@ namespace package { futures.push_back(std::async(std::launch::async, GetHomebrewCount)); futures.push_back(std::async(std::launch::async, GetMacPortsCount)); #elifdef _WIN32 - futures.push_back(std::async(std::launch::async, GetWinRTCount)); - futures.push_back(std::async(std::launch::async, GetChocolateyCount)); - futures.push_back(std::async(std::launch::async, GetScoopCount)); + futures.push_back(std::async(std::launch::async, CountWinGet)); + futures.push_back(std::async(std::launch::async, CountChocolatey)); + futures.push_back(std::async(std::launch::async, CountScoop)); #elif defined(__FreeBSD__) || defined(__DragonFly__) futures.push_back(std::async(std::launch::async, GetPkgNgCount)); #elifdef __NetBSD__ @@ -307,27 +334,27 @@ namespace package { #if defined(__linux__) || defined(__APPLE__) futures.push_back(std::async(std::launch::async, GetNixCount)); -#endif // __linux__ || __APPLE__ - futures.push_back(std::async(std::launch::async, GetCargoCount)); +#endif + futures.push_back(std::async(std::launch::async, CountCargo)); u64 totalCount = 0; bool oneSucceeded = false; - for (Future>& fut : futures) { + for (Future>& fut : futures) { try { - if (Result result = fut.get()) { + using enum util::error::DracErrorCode; + + if (Result result = fut.get()) { totalCount += *result; oneSucceeded = true; debug_log("Added {} packages. Current total: {}", *result, totalCount); - } else { - if (result.error().code != DracErrorCode::NotFound && result.error().code != DracErrorCode::ApiUnavailable && - result.error().code != DracErrorCode::NotSupported) { - error_at(result.error()); - } else - debug_at(result.error()); - } - } catch (const Exception& e) { - error_log("Caught exception while getting package count future: {}", e.what()); + } else if (result.error().code != NotFound && result.error().code != ApiUnavailable && + result.error().code != NotSupported) { + error_at(result.error()); + } else + debug_at(result.error()); + } catch (const Exception& exc) { + error_log("Caught exception while getting package count future: {}", exc.what()); } catch (...) { error_log("Caught unknown exception while getting package count future."); } } diff --git a/src/core/package.hpp b/src/core/package.hpp index a2acc24..a750efb 100644 --- a/src/core/package.hpp +++ b/src/core/package.hpp @@ -4,10 +4,9 @@ #include // glz::object #include // glz::detail::Object -// Include necessary type headers used in declarations #include "src/util/defs.hpp" #include "src/util/error.hpp" -#include "src/util/types.hpp" // Brings in Result, u64, etc. +#include "src/util/types.hpp" namespace package { namespace fs = std::filesystem; @@ -51,14 +50,14 @@ namespace package { * @return Result containing the total package count (u64) on success, * or a DracError if aggregation fails (individual errors logged). */ - fn GetTotalCount() -> Result; + fn GetTotalCount() -> Result; /** * @brief Gets package count from a database using SQLite. * @param pmInfo Information about the package manager database. * @return Result containing the count (u64) or a DracError. */ - fn GetCountFromDb(const PackageManagerInfo& pmInfo) -> Result; + fn GetCountFromDb(const PackageManagerInfo& pmInfo) -> Result; /** * @brief Gets package count by iterating entries in a directory, optionally filtering and subtracting. @@ -73,7 +72,7 @@ namespace package { const fs::path& dirPath, const String& fileExtensionFilter, bool subtractOne - ) -> Result; + ) -> Result; /** * @brief Gets package count by iterating entries in a directory, filtering by extension. @@ -83,7 +82,7 @@ namespace package { * @return Result containing the count (u64) or a DracError. Defaults subtractOne to false. */ fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath, const String& fileExtensionFilter) - -> Result; + -> Result; /** * @brief Gets package count by iterating entries in a directory, optionally subtracting one. @@ -92,7 +91,7 @@ namespace package { * @param subtractOne Subtract one from the final count. * @return Result containing the count (u64) or a DracError. Defaults fileExtensionFilter to "". */ - fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath, bool subtractOne) -> Result; + fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath, bool subtractOne) -> Result; /** * @brief Gets package count by iterating all entries in a directory. @@ -100,36 +99,36 @@ namespace package { * @param dirPath Path to the directory to iterate. * @return Result containing the count (u64) or a DracError. Defaults filter to "" and subtractOne to false. */ - fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath) -> Result; + fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath) -> Result; #ifdef __linux__ - fn GetDpkgCount() -> Result; - fn GetPacmanCount() -> Result; - fn GetMossCount() -> Result; - fn GetRpmCount() -> Result; - fn GetZypperCount() -> Result; - fn GetPortageCount() -> Result; - fn GetApkCount() -> Result; + fn GetDpkgCount() -> Result; + fn GetPacmanCount() -> Result; + fn GetMossCount() -> Result; + fn GetRpmCount() -> Result; + fn GetZypperCount() -> Result; + fn GetPortageCount() -> Result; + fn GetApkCount() -> Result; #elifdef __APPLE__ - fn GetHomebrewCount() -> Result; - fn GetMacPortsCount() -> Result; + fn GetHomebrewCount() -> Result; + fn GetMacPortsCount() -> Result; #elifdef _WIN32 - fn GetWinRTCount() -> Result; - fn GetChocolateyCount() -> Result; - fn GetScoopCount() -> Result; + fn CountWinGet() -> Result; + fn CountChocolatey() -> Result; + fn CountScoop() -> Result; #elif defined(__FreeBSD__) || defined(__DragonFly__) - fn GetPkgNgCount() -> Result; + fn GetPkgNgCount() -> Result; #elifdef __NetBSD__ - fn GetPkgSrcCount() -> Result; + fn GetPkgSrcCount() -> Result; #elifdef __HAIKU__ - fn GetHaikuCount() -> Result; + fn GetHaikuCount() -> Result; #elifdef __serenity__ - fn GetSerenityCount() -> Result; + fn GetSerenityCount() -> Result; #endif // Common (potentially cross-platform) #ifndef _WIN32 - fn GetNixCount() -> Result; + fn GetNixCount() -> Result; #endif - fn GetCargoCount() -> Result; + fn CountCargo() -> Result; } // namespace package diff --git a/src/core/system_data.cpp b/src/core/system_data.cpp index 6760c3e..9db9ac6 100644 --- a/src/core/system_data.cpp +++ b/src/core/system_data.cpp @@ -30,7 +30,7 @@ namespace { } } - fn getDate() -> Result { + fn getDate() -> Result { using std::chrono::system_clock; using util::types::String, util::types::usize, util::types::Err; @@ -68,21 +68,20 @@ namespace { namespace os { SystemData::SystemData(const Config& config) { using enum std::launch; + using package::GetTotalCount; using util::types::Future, util::types::Err; - using weather::Output; - Future> hostFut = std::async(async, GetHost); - Future> kernelFut = std::async(async, GetKernelVersion); - Future> osFut = std::async(async, GetOSVersion); - Future> memFut = std::async(async, GetMemInfo); - Future> deFut = std::async(async, GetDesktopEnvironment); - Future> wmFut = std::async(async, GetWindowManager); - Future> diskFut = std::async(async, GetDiskUsage); - Future> shellFut = std::async(async, GetShell); - Future> pkgFut = std::async(async, package::GetTotalCount); - Future> npFut = - std::async(config.nowPlaying.enabled ? async : deferred, GetNowPlaying); - Future> wthrFut = + Future> hostFut = std::async(async, GetHost); + Future> kernelFut = std::async(async, GetKernelVersion); + Future> osFut = std::async(async, GetOSVersion); + Future> memFut = std::async(async, GetMemInfo); + Future> deFut = std::async(async, GetDesktopEnvironment); + Future> wmFut = std::async(async, GetWindowManager); + Future> diskFut = std::async(async, GetDiskUsage); + Future> shellFut = std::async(async, GetShell); + Future> pkgFut = std::async(async, GetTotalCount); + Future> npFut = std::async(config.nowPlaying.enabled ? async : deferred, GetNowPlaying); + Future> wthrFut = std::async(config.weather.enabled ? async : deferred, [&config] { return config.weather.getWeatherInfo(); }); this->date = getDate(); diff --git a/src/core/system_data.hpp b/src/core/system_data.hpp index 9577036..3ec5f9a 100644 --- a/src/core/system_data.hpp +++ b/src/core/system_data.hpp @@ -34,7 +34,7 @@ constexpr u64 GIB = 1'073'741'824; * * @code{.cpp} * #include - * #include "system_data.h" // Assuming BytesToGiB is defined here + * #include "system_data.h" * * i32 main() { * BytesToGiB data_size{2'147'483'648}; // 2 GiB @@ -66,18 +66,18 @@ namespace os { * in order to display it at all at once during runtime. */ struct SystemData { - Result date; ///< Current date (e.g., "April 26th"). - Result host; ///< Host/product family (e.g., "MacBook Air"). - Result kernelVersion; ///< OS kernel version (e.g., "6.14.4"). - Result osVersion; ///< OS pretty name (e.g., "Ubuntu 24.04.2 LTS"). - Result memInfo; ///< Total physical RAM in bytes. - Result desktopEnv; ///< Desktop environment (e.g., "KDE"). - Result windowMgr; ///< Window manager (e.g., "KWin"). - Result diskUsage; ///< Used/Total disk space for root filesystem. - Result shell; ///< Name of the current user shell (e.g., "zsh"). - Result packageCount; ///< Total number of packages installed. - Result nowPlaying; ///< Result of fetching media info. - Result weather; ///< Result of fetching weather info. + Result date; ///< Current date (e.g., "April 26th"). + Result host; ///< Host/product family (e.g., "MacBook Air"). + Result kernelVersion; ///< OS kernel version (e.g., "6.14.4"). + Result osVersion; ///< OS pretty name (e.g., "Ubuntu 24.04.2 LTS"). + Result memInfo; ///< Total physical RAM in bytes. + Result desktopEnv; ///< Desktop environment (e.g., "KDE"). + Result windowMgr; ///< Window manager (e.g., "KWin"). + Result diskUsage; ///< Used/Total disk space for root filesystem. + Result shell; ///< Name of the current user shell (e.g., "zsh"). + Result packageCount; ///< Total number of packages installed. + Result nowPlaying; ///< Result of fetching media info. + Result weather; ///< Result of fetching weather info. /** * @brief Constructs a SystemData object and initializes its members. diff --git a/src/main.cpp b/src/main.cpp index 6b9690e..2dc9da1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,13 +1,17 @@ -#include // std::lround #include // std::format #include // ftxui::{Element, hbox, vbox, text, separator, filler, etc.} #include // ftxui::{Render} #include // ftxui::Color #include // ftxui::{Screen, Dimension::Full} #include // ftxui::string_width -#include // std::cout #include // std::ranges::{iota, to, transform} +#ifdef __cpp_lib_print + #include // std::print +#else + #include // std::cout +#endif + #include "src/config/config.hpp" #include "src/config/weather.hpp" #include "src/core/system_data.hpp" @@ -116,7 +120,7 @@ namespace { }; fn CreateColorCircles() -> Element { - fn colorView = + auto colorView = std::views::iota(0, 16) | std::views::transform([](i32 colorIndex) { return ftxui::hbox( { ftxui::text("◯") | ftxui::bold | ftxui::color(static_cast(colorIndex)), @@ -124,9 +128,7 @@ namespace { ); }); - Elements elementsContainer(std::ranges::begin(colorView), std::ranges::end(colorView)); - - return hbox(elementsContainer); + return hbox(Elements(std::ranges::begin(colorView), std::ranges::end(colorView))); } fn get_visual_width(const String& str) -> usize { return ftxui::string_width(str); } @@ -151,56 +153,64 @@ namespace { std::vector envInfoRows; // DE, WM if (data.date) - initialRows.push_back({ calendarIcon, "Date", *data.date }); + initialRows.push_back({ .icon = calendarIcon, .label = "Date", .value = *data.date }); if (weather.enabled && data.weather) { const weather::Output& weatherInfo = *data.weather; String weatherValue = weather.showTownName ? std::format("{}°F in {}", std::lround(weatherInfo.main.temp), weatherInfo.name) : std::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description); - initialRows.push_back({ weatherIcon, "Weather", std::move(weatherValue) }); - } + initialRows.push_back({ .icon = weatherIcon, .label = "Weather", .value = std::move(weatherValue) }); + } else if (weather.enabled && !data.weather.has_value()) + debug_at(data.weather.error()); if (data.host && !data.host->empty()) - systemInfoRows.push_back({ hostIcon, "Host", *data.host }); + systemInfoRows.push_back({ .icon = hostIcon, .label = "Host", .value = *data.host }); if (data.osVersion) - systemInfoRows.push_back({ osIcon, "OS", *data.osVersion }); + systemInfoRows.push_back({ .icon = osIcon, .label = "OS", .value = *data.osVersion }); if (data.kernelVersion) - systemInfoRows.push_back({ kernelIcon, "Kernel", *data.kernelVersion }); + systemInfoRows.push_back({ .icon = kernelIcon, .label = "Kernel", .value = *data.kernelVersion }); if (data.memInfo) - systemInfoRows.push_back({ memoryIcon, "RAM", std::format("{}", BytesToGiB { *data.memInfo }) }); + systemInfoRows.push_back( + { .icon = memoryIcon, .label = "RAM", .value = std::format("{}", BytesToGiB { *data.memInfo }) } + ); else if (!data.memInfo.has_value()) debug_at(data.memInfo.error()); if (data.diskUsage) systemInfoRows.push_back( - { diskIcon, - "Disk", - std::format("{}/{}", BytesToGiB { data.diskUsage->used_bytes }, BytesToGiB { data.diskUsage->total_bytes }) } + { + .icon = diskIcon, + .label = "Disk", + .value = + std::format("{}/{}", BytesToGiB { data.diskUsage->usedBytes }, BytesToGiB { data.diskUsage->totalBytes }), + } ); if (data.shell) - systemInfoRows.push_back({ shellIcon, "Shell", *data.shell }); + systemInfoRows.push_back({ .icon = shellIcon, .label = "Shell", .value = *data.shell }); if (data.packageCount) { if (*data.packageCount > 0) - systemInfoRows.push_back({ packageIcon, "Packages", std::format("{}", *data.packageCount) }); + systemInfoRows.push_back( + { .icon = packageIcon, .label = "Packages", .value = std::format("{}", *data.packageCount) } + ); else debug_log("Package count is 0, skipping"); } bool addedDe = false; if (data.desktopEnv && (!data.windowMgr || *data.desktopEnv != *data.windowMgr)) { - envInfoRows.push_back({ deIcon, "DE", *data.desktopEnv }); + envInfoRows.push_back({ .icon = deIcon, .label = "DE", .value = *data.desktopEnv }); addedDe = true; } if (data.windowMgr) { if (!addedDe || (data.desktopEnv && *data.desktopEnv != *data.windowMgr)) - envInfoRows.push_back({ wmIcon, "WM", *data.windowMgr }); + envInfoRows.push_back({ .icon = wmIcon, .label = "WM", .value = *data.windowMgr }); } bool nowPlayingActive = false; @@ -215,22 +225,23 @@ namespace { usize maxContentWidth = 0; - usize greetingWidth = get_visual_width_sv(userIcon) + get_visual_width_sv("Hello ") + get_visual_width(name) + + const usize greetingWidth = get_visual_width_sv(userIcon) + get_visual_width_sv("Hello ") + get_visual_width(name) + get_visual_width_sv("! "); maxContentWidth = std::max(maxContentWidth, greetingWidth); - usize paletteWidth = get_visual_width_sv(userIcon) + (16 * (get_visual_width_sv("◯") + get_visual_width_sv(" "))); - maxContentWidth = std::max(maxContentWidth, paletteWidth); + const usize paletteWidth = + get_visual_width_sv(userIcon) + (16 * (get_visual_width_sv("◯") + get_visual_width_sv(" "))); + maxContentWidth = std::max(maxContentWidth, paletteWidth); - usize iconActualWidth = get_visual_width_sv(userIcon); + const usize iconActualWidth = get_visual_width_sv(userIcon); - usize maxLabelWidthInitial = find_max_label_len(initialRows); - usize maxLabelWidthSystem = find_max_label_len(systemInfoRows); - usize maxLabelWidthEnv = find_max_label_len(envInfoRows); + const usize maxLabelWidthInitial = find_max_label_len(initialRows); + const usize maxLabelWidthSystem = find_max_label_len(systemInfoRows); + const usize maxLabelWidthEnv = find_max_label_len(envInfoRows); - usize requiredWidthInitialW = iconActualWidth + maxLabelWidthInitial; - usize requiredWidthSystemW = iconActualWidth + maxLabelWidthSystem; - usize requiredWidthEnvW = iconActualWidth + maxLabelWidthEnv; + const usize requiredWidthInitialW = iconActualWidth + maxLabelWidthInitial; + const usize requiredWidthSystemW = iconActualWidth + maxLabelWidthSystem; + const usize requiredWidthEnvW = iconActualWidth + maxLabelWidthEnv; fn calculateRowVisualWidth = [&](const RowInfo& row, const usize requiredLabelVisualWidth) -> usize { return requiredLabelVisualWidth + get_visual_width(row.value) + get_visual_width_sv(" "); @@ -245,7 +256,7 @@ namespace { for (const RowInfo& row : envInfoRows) maxContentWidth = std::max(maxContentWidth, calculateRowVisualWidth(row, requiredWidthEnvW)); - usize targetBoxWidth = maxContentWidth + 2; + const usize targetBoxWidth = maxContentWidth + 2; usize npFixedWidthLeft = 0; usize npFixedWidthRight = 0; @@ -289,9 +300,9 @@ namespace { content.push_back(separator() | color(ui::DEFAULT_THEME.border)); content.push_back(hbox({ text(String(paletteIcon)) | color(ui::DEFAULT_THEME.icon), CreateColorCircles() })); - bool section1Present = !initialRows.empty(); - bool section2Present = !systemInfoRows.empty(); - bool section3Present = !envInfoRows.empty(); + const bool section1Present = !initialRows.empty(); + const bool section2Present = !systemInfoRows.empty(); + const bool section3Present = !envInfoRows.empty(); if (section1Present) content.push_back(separator() | color(ui::DEFAULT_THEME.border)); @@ -342,7 +353,11 @@ fn main() -> i32 { Render(screen, document); screen.Print(); +#ifdef __cpp_lib_print + std::println(); +#else std::cout << '\n'; +#endif return 0; } diff --git a/src/os/bsd.cpp b/src/os/bsd.cpp index 32b687b..248b403 100644 --- a/src/os/bsd.cpp +++ b/src/os/bsd.cpp @@ -33,7 +33,7 @@ using util::error::DracError, util::error::DracErrorCode; namespace { #ifdef __FreeBSD__ - fn GetPathByPid(pid_t pid) -> Result { + fn GetPathByPid(pid_t pid) -> Result { Array exePathBuf; usize size = exePathBuf.size(); Array mib; @@ -57,7 +57,7 @@ namespace { } #endif - fn GetX11WindowManager() -> Result { + fn GetX11WindowManager() -> Result { using namespace xcb; const DisplayGuard conn; @@ -81,7 +81,7 @@ namespace { return std::format("Unknown Error Code ({})", err); }())); - fn internAtom = [&conn](const StringView name) -> Result { + fn internAtom = [&conn](const StringView name) -> Result { const ReplyGuard reply( intern_atom_reply(conn.get(), intern_atom(conn.get(), 0, static_cast(name.size()), name.data()), nullptr) ); @@ -94,9 +94,9 @@ namespace { return reply->atom; }; - const Result supportingWmCheckAtom = internAtom("_NET_SUPPORTING_WM_CHECK"); - const Result wmNameAtom = internAtom("_NET_WM_NAME"); - const Result utf8StringAtom = internAtom("UTF8_STRING"); + const Result supportingWmCheckAtom = internAtom("_NET_SUPPORTING_WM_CHECK"); + const Result wmNameAtom = internAtom("_NET_WM_NAME"); + const Result utf8StringAtom = internAtom("UTF8_STRING"); if (!supportingWmCheckAtom || !wmNameAtom || !utf8StringAtom) { if (!supportingWmCheckAtom) @@ -136,7 +136,7 @@ namespace { return String(nameData, length); } - fn GetWaylandCompositor() -> Result { + fn GetWaylandCompositor() -> Result { #ifndef __FreeBSD__ return "Wayland Compositor"; #else @@ -163,7 +163,7 @@ namespace { if (peerPid <= 0) return Err(DracError(DracErrorCode::PlatformSpecific, "Failed to obtain a valid peer PID")); - Result exePathResult = GetPathByPid(peerPid); + Result exePathResult = GetPathByPid(peerPid); if (!exePathResult) return Err(std::move(exePathResult).error()); @@ -199,7 +199,7 @@ namespace { namespace os { using util::helpers::GetEnv; - fn GetOSVersion() -> Result { + fn GetOSVersion() -> Result { constexpr CStr path = "/etc/os-release"; std::ifstream file(path); @@ -234,7 +234,7 @@ namespace os { return osName; } - fn GetMemInfo() -> Result { + fn GetMemInfo() -> Result { u64 mem = 0; usize size = sizeof(mem); @@ -247,10 +247,10 @@ namespace os { return mem; } - fn GetNowPlaying() -> Result { + fn GetNowPlaying() -> Result { using namespace dbus; - Result connectionResult = Connection::busGet(DBUS_BUS_SESSION); + Result connectionResult = Connection::busGet(DBUS_BUS_SESSION); if (!connectionResult) return Err(connectionResult.error()); @@ -259,12 +259,12 @@ namespace os { Option activePlayer = None; { - Result listNamesResult = + Result listNamesResult = Message::newMethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames"); if (!listNamesResult) return Err(listNamesResult.error()); - Result listNamesReplyResult = connection.sendWithReplyAndBlock(*listNamesResult, 100); + Result listNamesReplyResult = connection.sendWithReplyAndBlock(*listNamesResult, 100); if (!listNamesReplyResult) return Err(listNamesReplyResult.error()); @@ -292,7 +292,7 @@ namespace os { if (!activePlayer) return Err(DracError(DracErrorCode::NotFound, "No active MPRIS players found")); - Result msgResult = Message::newMethodCall( + Result msgResult = Message::newMethodCall( activePlayer->c_str(), "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get" ); @@ -304,7 +304,7 @@ namespace os { if (!msg.appendArgs("org.mpris.MediaPlayer2.Player", "Metadata")) return Err(DracError(DracErrorCode::InternalError, "Failed to append arguments to Properties.Get message")); - Result replyResult = connection.sendWithReplyAndBlock(msg, 100); + Result replyResult = connection.sendWithReplyAndBlock(msg, 100); if (!replyResult) return Err(replyResult.error()); @@ -378,20 +378,20 @@ namespace os { return MediaInfo(std::move(title), std::move(artist)); } - fn GetWindowManager() -> Result { + fn GetWindowManager() -> Result { if (!GetEnv("DISPLAY") && !GetEnv("WAYLAND_DISPLAY") && !GetEnv("XDG_SESSION_TYPE")) return Err(DracError(DracErrorCode::NotFound, "Could not find a graphical session")); - if (Result waylandResult = GetWaylandCompositor()) + if (Result waylandResult = GetWaylandCompositor()) return *waylandResult; - if (Result x11Result = GetX11WindowManager()) + if (Result x11Result = GetX11WindowManager()) return *x11Result; return Err(DracError(DracErrorCode::NotFound, "Could not detect window manager (Wayland/X11) or both failed")); } - fn GetDesktopEnvironment() -> Result { + fn GetDesktopEnvironment() -> Result { if (!GetEnv("DISPLAY") && !GetEnv("WAYLAND_DISPLAY") && !GetEnv("XDG_SESSION_TYPE")) return Err(DracError(DracErrorCode::NotFound, "Could not find a graphical session")); @@ -402,11 +402,11 @@ namespace os { return xdgDesktop; }) - .or_else([](const DracError&) -> Result { return GetEnv("DESKTOP_SESSION"); }); + .or_else([](const DracError&) -> Result { return GetEnv("DESKTOP_SESSION"); }); } - fn GetShell() -> Result { - if (const Result shellPath = GetEnv("SHELL")) { + fn GetShell() -> Result { + if (const Result shellPath = GetEnv("SHELL")) { // clang-format off constexpr Array, 5> shellMap {{ { "bash", "Bash" }, @@ -427,7 +427,7 @@ namespace os { return Err(DracError(DracErrorCode::NotFound, "Could not find SHELL environment variable")); } - fn GetHost() -> Result { + fn GetHost() -> Result { Array buffer {}; usize size = buffer.size(); @@ -459,7 +459,7 @@ namespace os { return String(buffer.data()); } - fn GetKernelVersion() -> Result { + fn GetKernelVersion() -> Result { utsname uts; if (uname(&uts) == -1) @@ -471,7 +471,7 @@ namespace os { return uts.release; } - fn GetDiskUsage() -> Result { + fn GetDiskUsage() -> Result { struct statvfs stat; if (statvfs("/", &stat) == -1) @@ -486,11 +486,11 @@ namespace os { namespace package { #ifdef __NetBSD__ - fn GetPkgSrcCount() -> Result { + fn GetPkgSrcCount() -> Result { return GetCountFromDirectory("pkgsrc", fs::current_path().root_path() / "usr" / "pkg" / "pkgdb", true); } #else - fn GetPkgNgCount() -> Result { + fn GetPkgNgCount() -> Result { const PackageManagerInfo pkgInfo = { .id = "pkgng", .dbPath = "/var/db/pkg/local.sqlite", diff --git a/src/os/haiku.cpp b/src/os/haiku.cpp index 5e03ab3..7feddb0 100644 --- a/src/os/haiku.cpp +++ b/src/os/haiku.cpp @@ -31,7 +31,7 @@ using util::error::DracError, util::error::DracErrorCode; using util::helpers::GetEnv; namespace os { - fn GetOSVersion() -> Result { + fn GetOSVersion() -> Result { BFile file; status_t status = file.SetTo("/boot/system/lib/libbe.so", B_READ_ONLY); @@ -58,7 +58,7 @@ namespace os { return std::format("Haiku {}", versionShortString); } - fn GetMemInfo() -> Result { + fn GetMemInfo() -> Result { system_info sysinfo; const status_t status = get_system_info(&sysinfo); @@ -68,16 +68,16 @@ namespace os { return static_cast(sysinfo.max_pages) * B_PAGE_SIZE; } - fn GetNowPlaying() -> Result { + fn GetNowPlaying() -> Result { return Err(DracError(DracErrorCode::NotSupported, "Now playing is not supported on Haiku")); } - fn GetWindowManager() -> Result { return "app_server"; } + fn GetWindowManager() -> Result { return "app_server"; } - fn GetDesktopEnvironment() -> Result { return "Haiku Desktop Environment"; } + fn GetDesktopEnvironment() -> Result { return "Haiku Desktop Environment"; } - fn GetShell() -> Result { - if (const Result shellPath = GetEnv("SHELL")) { + fn GetShell() -> Result { + if (const Result shellPath = GetEnv("SHELL")) { // clang-format off constexpr Array, 5> shellMap {{ { "bash", "Bash" }, @@ -98,7 +98,7 @@ namespace os { return Err(DracError(DracErrorCode::NotFound, "Could not find SHELL environment variable")); } - fn GetHost() -> Result { + fn GetHost() -> Result { Array hostnameBuffer {}; if (gethostname(hostnameBuffer.data(), hostnameBuffer.size()) != 0) @@ -111,7 +111,7 @@ namespace os { return String(hostnameBuffer.data(), hostnameBuffer.size()); } - fn GetKernelVersion() -> Result { + fn GetKernelVersion() -> Result { system_info sysinfo; const status_t status = get_system_info(&sysinfo); @@ -121,7 +121,7 @@ namespace os { return std::to_string(sysinfo.kernel_version); } - fn GetDiskUsage() -> Result { + fn GetDiskUsage() -> Result { struct statvfs stat; if (statvfs("/boot", &stat) == -1) @@ -135,7 +135,7 @@ namespace os { } // namespace os namespace package { - fn GetHaikuCount() -> Result { + fn GetHaikuCount() -> Result { BPackageKit::BPackageRoster roster; BPackageKit::BPackageInfoSet packageList; diff --git a/src/os/linux.cpp b/src/os/linux.cpp index 44be436..bd44e89 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -42,7 +42,7 @@ using util::error::DracError, util::error::DracErrorCode; using util::helpers::GetEnv; namespace { - fn GetX11WindowManager() -> Result { + fn GetX11WindowManager() -> Result { using namespace xcb; const DisplayGuard conn; @@ -66,7 +66,7 @@ namespace { return std::format("Unknown Error Code ({})", err); }())); - fn internAtom = [&conn](const StringView name) -> Result { + fn internAtom = [&conn](const StringView name) -> Result { const ReplyGuard reply( intern_atom_reply(conn.get(), intern_atom(conn.get(), 0, static_cast(name.size()), name.data()), nullptr) ); @@ -79,9 +79,9 @@ namespace { return reply->atom; }; - const Result supportingWmCheckAtom = internAtom("_NET_SUPPORTING_WM_CHECK"); - const Result wmNameAtom = internAtom("_NET_WM_NAME"); - const Result utf8StringAtom = internAtom("UTF8_STRING"); + const Result supportingWmCheckAtom = internAtom("_NET_SUPPORTING_WM_CHECK"); + const Result wmNameAtom = internAtom("_NET_WM_NAME"); + const Result utf8StringAtom = internAtom("UTF8_STRING"); if (!supportingWmCheckAtom || !wmNameAtom || !utf8StringAtom) { if (!supportingWmCheckAtom) @@ -121,7 +121,7 @@ namespace { return String(nameData, length); } - fn GetWaylandCompositor() -> Result { + fn GetWaylandCompositor() -> Result { const wl::DisplayGuard display; if (!display) @@ -194,7 +194,7 @@ namespace { } // namespace namespace os { - fn GetOSVersion() -> Result { + fn GetOSVersion() -> Result { constexpr CStr path = "/etc/os-release"; std::ifstream file(path); @@ -225,7 +225,7 @@ namespace os { return Err(DracError(DracErrorCode::NotFound, std::format("PRETTY_NAME line not found in {}", path))); } - fn GetMemInfo() -> Result { + fn GetMemInfo() -> Result { struct sysinfo info; if (sysinfo(&info) != 0) @@ -243,10 +243,10 @@ namespace os { return info.totalram * info.mem_unit; } - fn GetNowPlaying() -> Result { + fn GetNowPlaying() -> Result { using namespace dbus; - Result connectionResult = Connection::busGet(DBUS_BUS_SESSION); + Result connectionResult = Connection::busGet(DBUS_BUS_SESSION); if (!connectionResult) return Err(connectionResult.error()); @@ -255,12 +255,12 @@ namespace os { Option activePlayer = None; { - Result listNamesResult = + Result listNamesResult = Message::newMethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames"); if (!listNamesResult) return Err(listNamesResult.error()); - Result listNamesReplyResult = connection.sendWithReplyAndBlock(*listNamesResult, 100); + Result listNamesReplyResult = connection.sendWithReplyAndBlock(*listNamesResult, 100); if (!listNamesReplyResult) return Err(listNamesReplyResult.error()); @@ -288,7 +288,7 @@ namespace os { if (!activePlayer) return Err(DracError(DracErrorCode::NotFound, "No active MPRIS players found")); - Result msgResult = Message::newMethodCall( + Result msgResult = Message::newMethodCall( activePlayer->c_str(), "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get" ); @@ -300,7 +300,7 @@ namespace os { if (!msg.appendArgs("org.mpris.MediaPlayer2.Player", "Metadata")) return Err(DracError(DracErrorCode::InternalError, "Failed to append arguments to Properties.Get message")); - Result replyResult = connection.sendWithReplyAndBlock(msg, 100); + Result replyResult = connection.sendWithReplyAndBlock(msg, 100); if (!replyResult) return Err(replyResult.error()); @@ -374,17 +374,17 @@ namespace os { return MediaInfo(std::move(title), std::move(artist)); } - fn GetWindowManager() -> Result { - if (Result waylandResult = GetWaylandCompositor()) + fn GetWindowManager() -> Result { + if (Result waylandResult = GetWaylandCompositor()) return *waylandResult; - if (Result x11Result = GetX11WindowManager()) + if (Result x11Result = GetX11WindowManager()) return *x11Result; return Err(DracError(DracErrorCode::NotFound, "Could not detect window manager (Wayland/X11) or both failed")); } - fn GetDesktopEnvironment() -> Result { + fn GetDesktopEnvironment() -> Result { return GetEnv("XDG_CURRENT_DESKTOP") .transform([](String xdgDesktop) -> String { if (const usize colon = xdgDesktop.find(':'); colon != String::npos) @@ -392,11 +392,11 @@ namespace os { return xdgDesktop; }) - .or_else([](const DracError&) -> Result { return GetEnv("DESKTOP_SESSION"); }); + .or_else([](const DracError&) -> Result { return GetEnv("DESKTOP_SESSION"); }); } - fn GetShell() -> Result { - if (const Result shellPath = GetEnv("SHELL")) { + fn GetShell() -> Result { + if (const Result shellPath = GetEnv("SHELL")) { // clang-format off constexpr Array, 5> shellMap {{ { "bash", "Bash" }, @@ -417,11 +417,11 @@ namespace os { return Err(DracError(DracErrorCode::NotFound, "Could not find SHELL environment variable")); } - fn GetHost() -> Result { + fn GetHost() -> Result { constexpr CStr primaryPath = "/sys/class/dmi/id/product_family"; constexpr CStr fallbackPath = "/sys/class/dmi/id/product_name"; - fn readFirstLine = [&](const String& path) -> Result { + fn readFirstLine = [&](const String& path) -> Result { std::ifstream file(path); String line; @@ -438,8 +438,8 @@ namespace os { return line; }; - return readFirstLine(primaryPath).or_else([&](const DracError& primaryError) -> Result { - return readFirstLine(fallbackPath).or_else([&](const DracError& fallbackError) -> Result { + return readFirstLine(primaryPath).or_else([&](const DracError& primaryError) -> Result { + return readFirstLine(fallbackPath).or_else([&](const DracError& fallbackError) -> Result { return Err(DracError( DracErrorCode::InternalError, std::format( @@ -454,7 +454,7 @@ namespace os { }); } - fn GetKernelVersion() -> Result { + fn GetKernelVersion() -> Result { utsname uts; if (uname(&uts) == -1) @@ -466,7 +466,7 @@ namespace os { return uts.release; } - fn GetDiskUsage() -> Result { + fn GetDiskUsage() -> Result { struct statvfs stat; if (statvfs("/", &stat) == -1) @@ -482,18 +482,18 @@ namespace os { namespace package { using namespace std::string_literals; - fn GetDpkgCount() -> Result { + fn GetDpkgCount() -> Result { return GetCountFromDirectory("Dpkg", fs::current_path().root_path() / "var" / "lib" / "dpkg" / "info", ".list"s); } - fn GetMossCount() -> Result { + fn GetMossCount() -> Result { const PackageManagerInfo mossInfo = { .id = "moss", .dbPath = "/.moss/db/install", .countQuery = "SELECT COUNT(*) FROM meta", }; - Result countResult = GetCountFromDb(mossInfo); + Result countResult = GetCountFromDb(mossInfo); if (countResult) if (*countResult > 0) @@ -502,7 +502,7 @@ namespace package { return countResult; } - fn GetPacmanCount() -> Result { + fn GetPacmanCount() -> Result { return GetCountFromDirectory("Pacman", fs::current_path().root_path() / "var" / "lib" / "pacman" / "local", true); } } // namespace package diff --git a/src/os/macos.cpp b/src/os/macos.cpp index 97ad2a2..5aa2060 100644 --- a/src/os/macos.cpp +++ b/src/os/macos.cpp @@ -15,7 +15,7 @@ using namespace util::types; using util::error::DracError; -fn os::GetMemInfo() -> Result { +fn os::GetMemInfo() -> Result { u64 mem = 0; usize size = sizeof(mem); @@ -25,19 +25,19 @@ fn os::GetMemInfo() -> Result { return mem; } -fn os::GetNowPlaying() -> Result { return GetCurrentPlayingInfo(); } +fn os::GetNowPlaying() -> Result { return GetCurrentPlayingInfo(); } -fn os::GetOSVersion() -> Result { return GetMacOSVersion(); } +fn os::GetOSVersion() -> Result { return GetMacOSVersion(); } -fn os::GetDesktopEnvironment() -> Result { +fn os::GetDesktopEnvironment() -> Result { return "Aqua"; // TODO: Implement } -fn os::GetWindowManager() -> Result { +fn os::GetWindowManager() -> Result { return "Yabai"; // TODO: Implement } -fn os::GetKernelVersion() -> Result { +fn os::GetKernelVersion() -> Result { Array kernelVersion {}; usize kernelVersionLen = sizeof(kernelVersion); @@ -47,7 +47,7 @@ fn os::GetKernelVersion() -> Result { return kernelVersion.data(); } -fn os::GetHost() -> Result { +fn os::GetHost() -> Result { Array hwModel {}; usize hwModelLen = sizeof(hwModel); @@ -212,7 +212,7 @@ fn os::GetHost() -> Result { return String(iter->second); } -fn os::GetDiskUsage() -> Result { +fn os::GetDiskUsage() -> Result { struct statvfs vfs; if (statvfs("/", &vfs) != 0) @@ -222,11 +222,11 @@ fn os::GetDiskUsage() -> Result { .total_bytes = vfs.f_blocks * vfs.f_frsize }; } -fn os::GetPackageCount() -> Result { +fn os::GetPackageCount() -> Result { return Err(DracError(DracErrorCode::NotSupported, "Package count is not supported on macOS")); // TODO: Implement } -fn os::GetShell() -> Result { +fn os::GetShell() -> Result { return "Fish"; // TODO: Implement } diff --git a/src/os/macos/bridge.mm b/src/os/macos/bridge.mm index 8a26b41..51b3452 100644 --- a/src/os/macos/bridge.mm +++ b/src/os/macos/bridge.mm @@ -66,7 +66,7 @@ using MRMediaRemoteGetNowPlayingInfoFunction = ); } -+ (Result)macOSVersion { ++ (Result)macOSVersion { NSProcessInfo* processInfo = [NSProcessInfo processInfo]; NSOperatingSystemVersion osVersion = [processInfo operatingSystemVersion]; @@ -91,8 +91,8 @@ using MRMediaRemoteGetNowPlayingInfoFunction = extern "C++" { // NOLINTBEGIN(misc-use-internal-linkage) - fn GetCurrentPlayingInfo() -> Result { - __block Result result; + fn GetCurrentPlayingInfo() -> Result { + __block Result result; const dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); @@ -124,7 +124,7 @@ extern "C++" { return result; } - fn GetMacOSVersion() -> Result { return [Bridge macOSVersion]; } + fn GetMacOSVersion() -> Result { return [Bridge macOSVersion]; } // NOLINTEND(misc-use-internal-linkage) } diff --git a/src/os/os.hpp b/src/os/os.hpp index 4ca8cf9..a45772d 100644 --- a/src/os/os.hpp +++ b/src/os/os.hpp @@ -23,21 +23,21 @@ namespace os { * @return A Result containing the total RAM in bytes (u64) on success, * or a DracError on failure. */ - fn GetMemInfo() -> Result; + fn GetMemInfo() -> Result; /** * @brief Gets structured metadata about the currently playing media. * @return A Result containing the media information (MediaInfo struct) on success, * or a NowPlayingError (indicating player state or system error) on failure. */ - fn GetNowPlaying() -> Result; + fn GetNowPlaying() -> Result; /** * @brief Gets the "pretty" name of the operating system. * @details Examples: "Ubuntu 24.04.2 LTS", "Windows 11 Pro 24H2", "macOS 15 Sequoia". * @return A Result containing the OS version String on success, or a DracError on failure. */ - fn GetOSVersion() -> Result; + fn GetOSVersion() -> Result; /** * @brief Attempts to retrieve the desktop environment name. @@ -46,7 +46,7 @@ namespace os { * @return A Result containing the DE name String on success, * or a DracError on failure (e.g., permission error, API error). */ - fn GetDesktopEnvironment() -> Result; + fn GetDesktopEnvironment() -> Result; /** * @brief Attempts to retrieve the window manager name. @@ -55,7 +55,7 @@ namespace os { * @return A Result containing the detected WM name String on success, * or a DracError on failure (e.g., permission error, API error). */ - fn GetWindowManager() -> Result; + fn GetWindowManager() -> Result; /** * @brief Attempts to detect the current user shell name. @@ -64,7 +64,7 @@ namespace os { * @return A Result containing the shell name String on success, * or a DracError on failure (e.g., permission error, API error). */ - fn GetShell() -> Result; + fn GetShell() -> Result; /** * @brief Gets a system identifier, often the hardware model or product family. @@ -73,7 +73,7 @@ namespace os { * @return A Result containing the host/product identifier String on success, * or a DracError on failure (e.g., permission reading DMI/registry, API error). */ - fn GetHost() -> Result; + fn GetHost() -> Result; /** * @brief Gets the operating system's kernel version string. @@ -82,7 +82,7 @@ namespace os { * @return A Result containing the kernel version String on success, * or a DracError on failure. */ - fn GetKernelVersion() -> Result; + fn GetKernelVersion() -> Result; /** * @brief Gets the disk usage for the primary/root filesystem. @@ -90,5 +90,5 @@ namespace os { * @return A Result containing the DiskSpace struct (used/total bytes) on success, * or a DracError on failure (e.g., filesystem not found, permission error). */ - fn GetDiskUsage() -> Result; + fn GetDiskUsage() -> Result; } // namespace os diff --git a/src/os/serenity.cpp b/src/os/serenity.cpp index 3e96dc7..6dbb962 100644 --- a/src/os/serenity.cpp +++ b/src/os/serenity.cpp @@ -50,7 +50,7 @@ namespace { // NOLINTEND(readability-identifier-naming) }; - fn CountUniquePackages(const String& dbPath) -> Result { + fn CountUniquePackages(const String& dbPath) -> Result { std::ifstream dbFile(dbPath); if (!dbFile.is_open()) @@ -68,7 +68,7 @@ namespace { } // namespace namespace os { - fn GetOSVersion() -> Result { + fn GetOSVersion() -> Result { utsname uts; if (uname(&uts) == -1) @@ -77,7 +77,7 @@ namespace os { return uts.sysname; } - fn GetMemInfo() -> Result { + fn GetMemInfo() -> Result { CStr path = "/sys/kernel/memstat"; std::ifstream file(path); @@ -106,15 +106,15 @@ namespace os { return (data.physical_allocated + data.physical_available) * PAGE_SIZE; } - fn GetNowPlaying() -> Result { + fn GetNowPlaying() -> Result { return Err(DracError(DracErrorCode::NotSupported, "Now playing is not supported on SerenityOS")); } - fn GetWindowManager() -> Result { return "WindowManager"; } + fn GetWindowManager() -> Result { return "WindowManager"; } - fn GetDesktopEnvironment() -> Result { return "SerenityOS Desktop"; } + fn GetDesktopEnvironment() -> Result { return "SerenityOS Desktop"; } - fn GetShell() -> Result { + fn GetShell() -> Result { uid_t userId = getuid(); passwd* pw = getpwuid(userId); @@ -134,7 +134,7 @@ namespace os { return shell; } - fn GetHost() -> Result { + fn GetHost() -> Result { Array hostname_buffer; if (gethostname(hostname_buffer.data(), hostname_buffer.size()) != 0) @@ -143,7 +143,7 @@ namespace os { return String(hostname_buffer.data()); } - fn GetKernelVersion() -> Result { + fn GetKernelVersion() -> Result { utsname uts; if (uname(&uts) == -1) @@ -152,7 +152,7 @@ namespace os { return uts.release; } - fn GetDiskUsage() -> Result { + fn GetDiskUsage() -> Result { struct statvfs stat; if (statvfs("/", &stat) == -1) return Err(DracError::withErrno("statvfs call failed for '/'")); @@ -166,7 +166,7 @@ namespace os { } // namespace os namespace package { - fn GetSerenityCount() -> Result { return CountUniquePackages("/usr/Ports/installed.db"); } + fn GetSerenityCount() -> Result { return CountUniquePackages("/usr/Ports/installed.db"); } } // namespace package #endif // __serenity__ diff --git a/src/os/windows.cpp b/src/os/windows.cpp index 3b718f0..5f7b02c 100644 --- a/src/os/windows.cpp +++ b/src/os/windows.cpp @@ -2,20 +2,21 @@ // clang-format off #define WIN32_LEAN_AND_MEAN -#include -#include #include -#include - #include +#include +#include +#include + #include +#include #include #include #include -#include #include #include +#include "src/core/package.hpp" #include "src/util/error.hpp" #include "src/util/helpers.hpp" #include "src/util/logging.hpp" @@ -63,6 +64,7 @@ namespace { String value((type == REG_SZ || type == REG_EXPAND_SZ) ? dataSize - 1 : dataSize, '\0'); + // NOLINTNEXTLINE(*-pro-type-reinterpret-cast) - required here if (RegQueryValueExA(key, valueName.c_str(), nullptr, nullptr, reinterpret_cast(value.data()), &dataSize) != ERROR_SUCCESS) { RegCloseKey(key); @@ -81,7 +83,8 @@ namespace { std::unordered_map processMap; - const HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + // ReSharper disable once CppLocalVariableMayBeConst + HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnap == INVALID_HANDLE_VALUE) { error_log("FindShellInProcessTree: Failed snapshot, error {}", GetLastError()); @@ -92,9 +95,10 @@ namespace { pe32.dwSize = sizeof(PROCESSENTRY32); if (Process32First(hSnap, &pe32)) { + // NOLINTNEXTLINE(*-avoid-do-while) do { - String fullName = pe32.szExeFile; - String baseName; + const String fullName = pe32.szExeFile; + String baseName; const size_t lastSlash = fullName.find_last_of("\\/"); @@ -107,11 +111,11 @@ namespace { if (baseName.length() > 4 && baseName.ends_with(".exe")) baseName.resize(baseName.length() - 4); - processMap[pe32.th32ProcessID] = ProcessData { pe32.th32ParentProcessID, std::move(baseName) }; + processMap[pe32.th32ProcessID] = + ProcessData { .parentPid = pe32.th32ParentProcessID, .baseExeNameLower = std::move(baseName) }; } while (Process32Next(hSnap, &pe32)); - } else { + } else error_log("FindShellInProcessTree: Process32First failed, error {}", GetLastError()); - } CloseHandle(hSnap); @@ -165,7 +169,7 @@ namespace { } // namespace namespace os { - fn GetMemInfo() -> Result { + fn GetMemInfo() -> Result { MEMORYSTATUSEX memInfo; memInfo.dwLength = sizeof(MEMORYSTATUSEX); @@ -178,7 +182,7 @@ namespace os { )); } - fn GetNowPlaying() -> Result { + fn GetNowPlaying() -> Result { using namespace winrt::Windows::Media::Control; using namespace winrt::Windows::Foundation; @@ -200,7 +204,7 @@ namespace os { } catch (const winrt::hresult_error& e) { return Err(DracError(e)); } } - fn GetOSVersion() -> Result { + fn GetOSVersion() -> Result { try { const String regSubKey = R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)"; @@ -230,13 +234,14 @@ namespace os { } catch (const std::exception& e) { return Err(DracError(e)); } } - fn GetHost() -> Result { + fn GetHost() -> Result { return GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SYSTEM\HardwareConfig\Current)", "SystemFamily"); } - fn GetKernelVersion() -> Result { + fn GetKernelVersion() -> Result { if (const HMODULE ntdllHandle = GetModuleHandleW(L"ntdll.dll")) { - if (const auto rtlGetVersion = std::bit_cast(GetProcAddress(ntdllHandle, "RtlGetVersion"))) { + // NOLINTNEXTLINE(*-pro-type-reinterpret-cast) - required here + if (const auto rtlGetVersion = reinterpret_cast(GetProcAddress(ntdllHandle, "RtlGetVersion"))) { RTL_OSVERSIONINFOW osInfo = {}; osInfo.dwOSVersionInfoSize = sizeof(osInfo); @@ -250,7 +255,7 @@ namespace os { return Err(DracError(DracErrorCode::NotFound, "Could not determine kernel version using RtlGetVersion")); } - fn GetWindowManager() -> Result { + fn GetWindowManager() -> Result { BOOL compositionEnabled = FALSE; if (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled))) @@ -259,7 +264,7 @@ namespace os { return Err(DracError(DracErrorCode::NotFound, "Failed to get window manager (DwmIsCompositionEnabled failed")); } - fn GetDesktopEnvironment() -> Result { + fn GetDesktopEnvironment() -> Result { const String buildStr = GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "CurrentBuildNumber"); @@ -301,18 +306,17 @@ namespace os { } catch (...) { return Err(DracError(DracErrorCode::ParseError, "Failed to parse CurrentBuildNumber")); } } - fn GetShell() -> Result { + fn GetShell() -> Result { using util::helpers::GetEnv; - if (const Result msystemResult = GetEnv("MSYSTEM"); msystemResult && !msystemResult->empty()) { + if (const Result msystemResult = GetEnv("MSYSTEM"); msystemResult && !msystemResult->empty()) { String shellPath; - if (const Result shellResult = GetEnv("SHELL"); shellResult && !shellResult->empty()) { + if (const Result shellResult = GetEnv("SHELL"); shellResult && !shellResult->empty()) shellPath = *shellResult; - } else if (const Result loginShellResult = GetEnv("LOGINSHELL"); - loginShellResult && !loginShellResult->empty()) { + else if (const Result loginShellResult = GetEnv("LOGINSHELL"); + loginShellResult && !loginShellResult->empty()) shellPath = *loginShellResult; - } if (!shellPath.empty()) { const usize lastSlash = shellPath.find_last_of("\\/"); @@ -341,20 +345,51 @@ namespace os { return Err(DracError(DracErrorCode::NotFound, "Shell not found")); } - fn GetDiskUsage() -> Result { + fn GetDiskUsage() -> Result { ULARGE_INTEGER freeBytes, totalBytes; if (GetDiskFreeSpaceExW(L"C:\\", nullptr, &totalBytes, &freeBytes)) - return DiskSpace { .used_bytes = totalBytes.QuadPart - freeBytes.QuadPart, .total_bytes = totalBytes.QuadPart }; + return DiskSpace { .usedBytes = totalBytes.QuadPart - freeBytes.QuadPart, .totalBytes = totalBytes.QuadPart }; return Err(DracError(util::error::DracErrorCode::NotFound, "Failed to get disk usage")); } +} // namespace os - fn GetPackageCount() -> Result { +namespace package { + using util::helpers::GetEnv; + + fn CountChocolatey() -> Result { + const fs::path chocoPath = fs::path(GetEnv("ChocolateyInstall").value_or("C:\\ProgramData\\chocolatey")) / "lib"; + + if (!fs::exists(chocoPath) || !fs::is_directory(chocoPath)) + return Err( + DracError(DracErrorCode::NotFound, std::format("Chocolatey directory not found: {}", chocoPath.string())) + ); + + return GetCountFromDirectory("Chocolatey", chocoPath); + } + + fn CountScoop() -> Result { + fs::path scoopAppsPath; + + if (const Result scoopEnvPath = GetEnv("SCOOP")) + scoopAppsPath = fs::path(*scoopEnvPath) / "apps"; + else if (const Result userProfilePath = GetEnv("USERPROFILE")) + scoopAppsPath = fs::path(*userProfilePath) / "scoop" / "apps"; + else + return Err(DracError( + DracErrorCode::NotFound, + "Could not determine Scoop installation directory (SCOOP and USERPROFILE environment variables not found)" + )); + + return GetCountFromDirectory("Scoop", scoopAppsPath, true); + } + + fn CountWinGet() -> Result { try { return std::ranges::distance(winrt::Windows::Management::Deployment::PackageManager().FindPackagesForUser(L"")); } catch (const winrt::hresult_error& e) { return Err(DracError(e)); } } -} // namespace os +} // namespace package #endif diff --git a/src/util/cache.hpp b/src/util/cache.hpp index b43e9d1..025e4fc 100644 --- a/src/util/cache.hpp +++ b/src/util/cache.hpp @@ -25,24 +25,33 @@ namespace util::cache { * 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 { + inline fn GetCachePath(const String& cache_key) -> Result { 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); + + const fs::path cacheDir = fs::temp_directory_path(errc) / "draconis++"; + + if (!fs::exists(cacheDir, errc)) { + if (errc) + return Err(DracError(DracErrorCode::IoError, "Failed to check existence of cache directory: " + errc.message()) + ); + + fs::create_directories(cacheDir, errc); + + if (errc) + return Err(DracError(DracErrorCode::IoError, "Failed to create cache directory: " + errc.message())); + } 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"); } @@ -53,18 +62,17 @@ namespace util::cache { * @return Result containing the deserialized object of type T on success, or a DracError on failure. */ template - fn ReadCache(const String& cache_key) -> Result { - Result cachePathResult = GetCachePath(cache_key); + fn ReadCache(const String& cache_key) -> Result { + Result cachePathResult = GetCachePath(cache_key); if (!cachePathResult) return Err(cachePathResult.error()); const fs::path& cachePath = *cachePathResult; if (std::error_code existsEc; !fs::exists(cachePath, existsEc) || existsEc) { - if (existsEc) { - // Log if there was an error checking existence, but still return NotFound + if (existsEc) warn_log("Error checking existence of cache file '{}': {}", cachePath.string(), existsEc.message()); - } + return Err(DracError(DracErrorCode::NotFound, "Cache file not found: " + cachePath.string())); } @@ -72,17 +80,13 @@ namespace util::cache { 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(ifs)), std::istreambuf_iterator()); - ifs.close(); // Close the file as soon as content is read + ifs.close(); 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, "Cache type T must be default constructible for Glaze."); T result {}; @@ -98,14 +102,12 @@ namespace util::cache { )); } - 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()) @@ -123,24 +125,20 @@ namespace util::cache { * @return Result containing void on success, or a DracError on failure. */ template - fn WriteCache(const String& cache_key, const T& data) -> Result { - Result cachePathResult = GetCachePath(cache_key); + fn WriteCache(const String& cache_key, const T& data) -> Result<> { + Result 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()); + tempPath += ".tmp"; 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; - DecayedT dataToSerialize = data; // Make a copy if needed for non-const Glaze operations + DecayedT dataToSerialize = data; if (glz::error_ctx glazeErr = glz::write_beve(dataToSerialize, binaryBuffer); glazeErr) { return Err(DracError( @@ -154,7 +152,6 @@ namespace util::cache { )); } - // Scope for ofstream to ensure it's closed before rename { std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc); if (!ofs.is_open()) @@ -163,22 +160,18 @@ namespace util::cache { ofs.write(binaryBuffer.data(), static_cast(binaryBuffer.size())); if (!ofs) { - // Attempt cleanup before returning error std::error_code removeEc; - fs::remove(tempPath, removeEc); // Ignore error on cleanup attempt + fs::remove(tempPath, removeEc); 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 renameEc; fs::rename(tempPath, cachePath, renameEc); if (renameEc) { - // If rename failed, attempt to clean up the temp file std::error_code removeEc; - fs::remove(tempPath, removeEc); // Ignore error on cleanup attempt + fs::remove(tempPath, removeEc); return Err(DracError( DracErrorCode::IoError, std::format( @@ -190,26 +183,24 @@ namespace util::cache { )); } - debug_log("Successfully wrote cache for key '{}'.", cache_key); - return {}; // Success + return {}; } catch (const std::ios_base::failure& e) { std::error_code removeEc; - fs::remove(tempPath, removeEc); // Cleanup attempt + 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); // Cleanup attempt + fs::remove(tempPath, removeEc); 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 + fs::remove(tempPath, removeEc); return Err(DracError(DracErrorCode::Other, "Unknown error writing cache file: " + tempPath.string())); } } - } // namespace util::cache diff --git a/src/util/error.hpp b/src/util/error.hpp index b1ea4db..88064c8 100644 --- a/src/util/error.hpp +++ b/src/util/error.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include // std::{unexpected, expected} #include // std::source_location #include // std::error_code @@ -11,115 +11,146 @@ #include "src/util/types.hpp" -namespace util::error { - using types::u8, types::i32, types::String, types::StringView, types::Exception; +#include "include/matchit.h" - /** - * @enum DracErrorCode - * @brief Error codes for general OS-level operations. - */ - enum class DracErrorCode : u8 { - ApiUnavailable, ///< A required OS service/API is unavailable or failed unexpectedly at runtime. - InternalError, ///< An error occurred within the application's OS abstraction code logic. - InvalidArgument, ///< An invalid argument was passed to a function or method. - IoError, ///< General I/O error (filesystem, pipes, etc.). - NetworkError, ///< A network-related error occurred (e.g., DNS resolution, connection failure). - NotFound, ///< A required resource (file, registry key, device, API endpoint) was not found. - NotSupported, ///< The requested operation is not supported on this platform, version, or configuration. - Other, ///< A generic or unclassified error originating from the OS or an external library. - OutOfMemory, ///< The system ran out of memory or resources to complete the operation. - ParseError, ///< Failed to parse data obtained from the OS (e.g., file content, API output). - PermissionDenied, ///< Insufficient permissions to perform the operation. - PlatformSpecific, ///< An unmapped error specific to the underlying OS platform occurred (check message). - Timeout, ///< An operation timed out (e.g., waiting for IPC reply). - }; +namespace util { + namespace error { + using types::u8, types::i32, types::String, types::StringView, types::Exception; - /** - * @struct DracError - * @brief Holds structured information about an OS-level error. - * - * Used as the error type in Result for many os:: functions. - */ - struct DracError { - // ReSharper disable CppDFANotInitializedField - String message; ///< A descriptive error message, potentially including platform details. - DracErrorCode code; ///< The general category of the error. - std::source_location location; ///< The source location where the error occurred (file, line, function). - // ReSharper restore CppDFANotInitializedField + /** + * @enum DracErrorCode + * @brief Error codes for general OS-level operations. + */ + enum class DracErrorCode : u8 { + ApiUnavailable, ///< A required OS service/API is unavailable or failed unexpectedly at runtime. + InternalError, ///< An error occurred within the application's OS abstraction code logic. + InvalidArgument, ///< An invalid argument was passed to a function or method. + IoError, ///< General I/O error (filesystem, pipes, etc.). + NetworkError, ///< A network-related error occurred (e.g., DNS resolution, connection failure). + NotFound, ///< A required resource (file, registry key, device, API endpoint) was not found. + NotSupported, ///< The requested operation is not supported on this platform, version, or configuration. + Other, ///< A generic or unclassified error originating from the OS or an external library. + OutOfMemory, ///< The system ran out of memory or resources to complete the operation. + ParseError, ///< Failed to parse data obtained from the OS (e.g., file content, API output). + PermissionDenied, ///< Insufficient permissions to perform the operation. + PlatformSpecific, ///< An unmapped error specific to the underlying OS platform occurred (check message). + Timeout, ///< An operation timed out (e.g., waiting for IPC reply). + }; - DracError(const DracErrorCode errc, String msg, const std::source_location& loc = std::source_location::current()) - : message(std::move(msg)), code(errc), location(loc) {} + /** + * @struct DracError + * @brief Holds structured information about an OS-level error. + * + * Used as the error type in Result for many os:: functions. + */ + struct DracError { + // ReSharper disable CppDFANotInitializedField + String message; ///< A descriptive error message, potentially including platform details. + DracErrorCode code; ///< The general category of the error. + std::source_location location; ///< The source location where the error occurred (file, line, function). + // ReSharper restore CppDFANotInitializedField - explicit DracError(const Exception& exc, const std::source_location& loc = std::source_location::current()) - : message(exc.what()), code(DracErrorCode::InternalError), location(loc) {} + DracError(const DracErrorCode errc, String msg, const std::source_location& loc = std::source_location::current()) + : message(std::move(msg)), code(errc), location(loc) {} - explicit DracError(const std::error_code& errc, const std::source_location& loc = std::source_location::current()) - : message(errc.message()), location(loc) { - using enum DracErrorCode; - using enum std::errc; + explicit DracError(const Exception& exc, const std::source_location& loc = std::source_location::current()) + : message(exc.what()), code(DracErrorCode::InternalError), location(loc) {} - switch (static_cast(errc.value())) { - case permission_denied: code = PermissionDenied; break; - case no_such_file_or_directory: code = NotFound; break; - case timed_out: code = Timeout; break; - case io_error: code = IoError; break; - case network_unreachable: - case network_down: - case connection_refused: code = NetworkError; break; - case not_supported: code = NotSupported; break; - default: code = errc.category() == std::generic_category() ? InternalError : PlatformSpecific; break; + explicit DracError(const std::error_code& errc, const std::source_location& loc = std::source_location::current()) + : message(errc.message()), location(loc) { + using namespace matchit; + using enum DracErrorCode; + using enum std::errc; + + code = match(errc)( + is | or_(file_too_large, io_error) = IoError, + is | invalid_argument = InvalidArgument, + is | not_enough_memory = OutOfMemory, + is | or_(address_family_not_supported, operation_not_supported, not_supported) = NotSupported, + is | or_(network_unreachable, network_down, connection_refused) = NetworkError, + is | or_(no_such_file_or_directory, not_a_directory, is_a_directory, file_exists) = NotFound, + is | permission_denied = PermissionDenied, + is | timed_out = Timeout, + is | _ = errc.category() == std::generic_category() ? InternalError : PlatformSpecific + ); } - } + #ifdef _WIN32 - explicit DracError(const winrt::hresult_error& e) : message(winrt::to_string(e.message())) { - switch (e.code()) { - case E_ACCESSDENIED: code = DracErrorCode::PermissionDenied; break; - case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND): - case HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND): - case HRESULT_FROM_WIN32(ERROR_SERVICE_NOT_FOUND): code = DracErrorCode::NotFound; break; - case HRESULT_FROM_WIN32(ERROR_TIMEOUT): - case HRESULT_FROM_WIN32(ERROR_SEM_TIMEOUT): code = DracErrorCode::Timeout; break; - case HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED): code = DracErrorCode::NotSupported; break; - default: code = DracErrorCode::PlatformSpecific; break; + explicit DracError(const winrt::hresult_error& e) : message(winrt::to_string(e.message())) { + using namespace matchit; + using enum DracErrorCode; + + fn fromWin32 = [](const types::u32 x) -> HRESULT { return HRESULT_FROM_WIN32(x); }; + + code = match(e.code())( + is | or_(E_ACCESSDENIED, fromWin32(ERROR_ACCESS_DENIED)) = PermissionDenied, + is | fromWin32(ERROR_FILE_NOT_FOUND) = NotFound, + is | fromWin32(ERROR_PATH_NOT_FOUND) = NotFound, + is | fromWin32(ERROR_SERVICE_NOT_FOUND) = NotFound, + is | fromWin32(ERROR_TIMEOUT) = Timeout, + is | fromWin32(ERROR_SEM_TIMEOUT) = Timeout, + is | fromWin32(ERROR_NOT_SUPPORTED) = NotSupported, + is | _ = PlatformSpecific + ); } - } #else - DracError(const DracErrorCode code_hint, const int errno_val) - : message(std::system_category().message(errno_val)), code(code_hint) { - using enum DracErrorCode; + DracError(const DracErrorCode code_hint, const int errno_val) + : message(std::system_category().message(errno_val)), code(code_hint) { + using enum DracErrorCode; - switch (errno_val) { - case EACCES: code = PermissionDenied; break; - case ENOENT: code = NotFound; break; - case ETIMEDOUT: code = Timeout; break; - case ENOTSUP: code = NotSupported; break; - default: code = PlatformSpecific; break; - } - } - - static auto withErrno(const String& context, const std::source_location& loc = std::source_location::current()) - -> DracError { - const i32 errNo = errno; - const String msg = std::system_category().message(errNo); - const String fullMsg = std::format("{}: {}", context, msg); - - const DracErrorCode code = [&errNo] { - switch (errNo) { - case EACCES: - case EPERM: return DracErrorCode::PermissionDenied; - case ENOENT: return DracErrorCode::NotFound; - case ETIMEDOUT: return DracErrorCode::Timeout; - case ENOTSUP: return DracErrorCode::NotSupported; - case EIO: return DracErrorCode::IoError; - case ECONNREFUSED: - case ENETDOWN: - case ENETUNREACH: return DracErrorCode::NetworkError; - default: return DracErrorCode::PlatformSpecific; + switch (errno_val) { + case EACCES: code = PermissionDenied; break; + case ENOENT: code = NotFound; break; + case ETIMEDOUT: code = Timeout; break; + case ENOTSUP: code = NotSupported; break; + default: code = PlatformSpecific; break; } - }(); + } - return DracError { code, fullMsg, loc }; - } + static auto withErrno(const String& context, const std::source_location& loc = std::source_location::current()) + -> DracError { + const i32 errNo = errno; + const String msg = std::system_category().message(errNo); + const String fullMsg = std::format("{}: {}", context, msg); + + const DracErrorCode code = [&errNo] { + switch (errNo) { + case EACCES: + case EPERM: return DracErrorCode::PermissionDenied; + case ENOENT: return DracErrorCode::NotFound; + case ETIMEDOUT: return DracErrorCode::Timeout; + case ENOTSUP: return DracErrorCode::NotSupported; + case EIO: return DracErrorCode::IoError; + case ECONNREFUSED: + case ENETDOWN: + case ENETUNREACH: return DracErrorCode::NetworkError; + default: return DracErrorCode::PlatformSpecific; + } + }(); + + return DracError { code, fullMsg, loc }; + } #endif - }; -} // namespace util::error + }; + } // namespace error + + namespace types { + /** + * @typedef Result + * @brief Alias for std::expected. Represents a value that can either be + * a success value of type Tp or an error value of type Er. + * @tparam Tp The type of the success value. + * @tparam Er The type of the error value. + */ + template + using Result = std::expected; + + /** + * @typedef Err + * @brief Alias for std::unexpected. Used to construct a Result in an error state. + * @tparam Er The type of the error value. + */ + template + using Err = std::unexpected; + } // namespace types +} // namespace util \ No newline at end of file diff --git a/src/util/helpers.hpp b/src/util/helpers.hpp index 4414d7f..86da3ee 100644 --- a/src/util/helpers.hpp +++ b/src/util/helpers.hpp @@ -14,7 +14,7 @@ namespace util::helpers { * @return A Result containing the value of the environment variable as a String, * or an EnvError if an error occurred. */ - [[nodiscard]] inline fn GetEnv(CStr name) -> Result { + [[nodiscard]] inline fn GetEnv(CStr name) -> Result { #ifdef _WIN32 using types::i32, types::usize, types::UniquePointer; diff --git a/src/util/logging.hpp b/src/util/logging.hpp index b00abd3..c534610 100644 --- a/src/util/logging.hpp +++ b/src/util/logging.hpp @@ -5,10 +5,15 @@ #include // std::filesystem::path #include // std::format #include // ftxui::Color -#include // std::cout #include // std::{mutex, lock_guard} #include // std::forward +#ifdef __cpp_lib_print + #include // std::print +#else + #include // std::cout +#endif + #ifndef NDEBUG #include // std::source_location #endif @@ -17,6 +22,8 @@ #include "src/util/error.hpp" #include "src/util/types.hpp" +#include "ftxui/dom/elements.hpp" + namespace util::logging { using types::usize, types::u8, types::i32, types::i64, types::CStr, types::String, types::StringView, types::Array, types::Option, types::None, types::Mutex, types::LockGuard; @@ -118,13 +125,15 @@ namespace util::logging { * @return FTXUI color code */ constexpr fn GetLevelColor(const LogLevel level) -> ftxui::Color::Palette16 { - switch (level) { - case LogLevel::Debug: return LogLevelConst::DEBUG_COLOR; - case LogLevel::Info: return LogLevelConst::INFO_COLOR; - case LogLevel::Warn: return LogLevelConst::WARN_COLOR; - case LogLevel::Error: return LogLevelConst::ERROR_COLOR; - default: std::unreachable(); - } + using namespace matchit; + using enum LogLevel; + + return match(level)( + is | Debug = LogLevelConst::DEBUG_COLOR, + is | Info = LogLevelConst::INFO_COLOR, + is | Warn = LogLevelConst::WARN_COLOR, + is | Error = LogLevelConst::ERROR_COLOR + ); } /** @@ -133,13 +142,15 @@ namespace util::logging { * @return String representation */ constexpr fn GetLevelString(const LogLevel level) -> StringView { - switch (level) { - case LogLevel::Debug: return LogLevelConst::DEBUG_STR; - case LogLevel::Info: return LogLevelConst::INFO_STR; - case LogLevel::Warn: return LogLevelConst::WARN_STR; - case LogLevel::Error: return LogLevelConst::ERROR_STR; - default: std::unreachable(); - } + using namespace matchit; + using enum LogLevel; + + return match(level)( + is | Debug = LogLevelConst::DEBUG_STR, + is | Info = LogLevelConst::INFO_STR, + is | Warn = LogLevelConst::WARN_STR, + is | Error = LogLevelConst::ERROR_STR + ); } // ReSharper disable once CppDoxygenUnresolvedReference @@ -180,7 +191,8 @@ namespace util::logging { if (localtime_r(&nowTt, &localTm) != nullptr) { #endif Array timeBuffer {}; - auto formattedTime = + + const usize formattedTime = std::strftime(timeBuffer.data(), sizeof(timeBuffer), LogLevelConst::TIMESTAMP_FORMAT, &localTm); if (formattedTime > 0) { @@ -188,11 +200,10 @@ namespace util::logging { } else { try { timestamp = std::format("{:%X}", nowTp); - } catch (const std::format_error& fmt_err) { timestamp = "??:??:?? (fmt_err)"; } + } catch ([[maybe_unused]] const std::format_error& fmtErr) { timestamp = "??:??:??"; } } - } else { - timestamp = "??:??:?? (conv_err)"; - } + } else + timestamp = "??:??:??"; const String message = std::format(fmt, std::forward(args)...); @@ -203,16 +214,28 @@ namespace util::logging { message ); +#ifdef __cpp_lib_print + std::print("{}", mainLogLine); +#else std::cout << mainLogLine; +#endif #ifndef NDEBUG const String fileLine = std::format(LogLevelConst::FILE_LINE_FORMAT, path(loc.file_name()).lexically_normal().string(), loc.line()); const String fullDebugLine = std::format("{}{}", LogLevelConst::DEBUG_LINE_PREFIX, fileLine); + #ifdef __cpp_lib_print + std::print("\n{}", Italic(Colorize(fullDebugLine, LogLevelConst::DEBUG_INFO_COLOR))); + #else std::cout << '\n' << Italic(Colorize(fullDebugLine, LogLevelConst::DEBUG_INFO_COLOR)); + #endif #endif +#ifdef __cpp_lib_print + std::println("{}", LogLevelConst::RESET_CODE); +#else std::cout << LogLevelConst::RESET_CODE << '\n'; +#endif } template diff --git a/src/util/types.hpp b/src/util/types.hpp index 5ae3611..2490dc7 100644 --- a/src/util/types.hpp +++ b/src/util/types.hpp @@ -1,7 +1,6 @@ #pragma once #include // std::array (Array) -#include // std::expected (Result) #include // std::future (Future) #include // std::map (Map) #include // std::shared_ptr and std::unique_ptr (SharedPointer, UniquePointer) @@ -12,6 +11,8 @@ #include // std::pair (Pair) #include // std::vector (Vec) +#include "include/matchit.h" + namespace util::types { using u8 = std::uint8_t; ///< 8-bit unsigned integer. using u16 = std::uint16_t; ///< 16-bit unsigned integer. @@ -40,24 +41,6 @@ namespace util::types { inline constexpr std::nullopt_t None = std::nullopt; ///< Represents an empty optional value. - /** - * @typedef Result - * @brief Alias for std::expected. Represents a value that can either be - * a success value of type Tp or an error value of type Er. - * @tparam Tp The type of the success value. - * @tparam Er The type of the error value. - */ - template - using Result = std::expected; - - /** - * @typedef Err - * @brief Alias for std::unexpected. Used to construct a Result in an error state. - * @tparam Er The type of the error value. - */ - template - using Err = std::unexpected; - /** * @typedef Option * @brief Alias for std::optional. Represents a value that may or may not be present. @@ -133,8 +116,8 @@ namespace util::types { * Used as the success type for os::GetDiskUsage. */ struct DiskSpace { - u64 used_bytes; ///< Currently used disk space in bytes. - u64 total_bytes; ///< Total disk space in bytes. + u64 usedBytes; ///< Currently used disk space in bytes. + u64 totalBytes; ///< Total disk space in bytes. }; /** diff --git a/src/wrappers/dbus.hpp b/src/wrappers/dbus.hpp index ab38cd4..8daa4c9 100644 --- a/src/wrappers/dbus.hpp +++ b/src/wrappers/dbus.hpp @@ -265,7 +265,7 @@ namespace dbus { * @return Result containing a MessageGuard on success, or DraconisError on failure. */ static fn newMethodCall(const char* destination, const char* path, const char* interface, const char* method) - -> Result { + -> Result { DBusMessage* rawMsg = dbus_message_new_method_call(destination, path, interface, method); if (!rawMsg) @@ -331,7 +331,7 @@ namespace dbus { * @return Result containing the reply MessageGuard on success, or DraconisError on failure. */ [[nodiscard]] fn sendWithReplyAndBlock(const Message& message, const i32 timeout_milliseconds = 1000) const - -> Result { + -> Result { if (!m_conn || !message.get()) return Err( DracError(DracErrorCode::InvalidArgument, "Invalid connection or message provided to sendWithReplyAndBlock") @@ -371,7 +371,7 @@ namespace dbus { * @param bus_type The type of bus (DBUS_BUS_SESSION or DBUS_BUS_SYSTEM). * @return Result containing a ConnectionGuard on success, or DraconisError on failure. */ - static fn busGet(const DBusBusType bus_type) -> Result { + static fn busGet(const DBusBusType bus_type) -> Result { Error err; DBusConnection* rawConn = dbus_bus_get(bus_type, err.get());