From e127a89e064117353423758c466bf0b99c676985 Mon Sep 17 00:00:00 2001 From: pupbrained Date: Thu, 8 May 2025 00:53:56 -0400 Subject: [PATCH] woof --- include/matchit.hpp | 3 +- src/core/package.cpp | 161 ++++++++++++++++++++++----------------- src/core/package.hpp | 18 ++--- src/main.cpp | 2 +- src/os/linux.cpp | 112 ++++++++++++++++++++++----- src/os/os.hpp | 4 +- src/wrappers/wayland.hpp | 16 ++-- 7 files changed, 204 insertions(+), 112 deletions(-) diff --git a/include/matchit.hpp b/include/matchit.hpp index ed34766..c2a8747 100644 --- a/include/matchit.hpp +++ b/include/matchit.hpp @@ -10,10 +10,11 @@ #include #include #include -#include +#include #include #include #include +#include #include #include "src/util/defs.hpp" diff --git a/src/core/package.cpp b/src/core/package.cpp index b1fbf25..2bfbe4c 100644 --- a/src/core/package.cpp +++ b/src/core/package.cpp @@ -1,20 +1,20 @@ #include "package.hpp" -#if !defined(__serenity_) && !defined(_WIN32) +#if !defined(__serenity__) && !defined(_WIN32) #include // SQLite::{Database, OPEN_READONLY} #include // SQLite::Exception #include // SQLite::Statement #endif #ifdef __linux__ - #include + #include // pugi::{xml_document, xml_node, xml_parse_result} #endif #include // std::chrono #include // std::filesystem #include // std::format #include // std::{async, future, launch} -#include // std::error_code +#include // std::{errc, error_code} #include "src/util/cache.hpp" #include "src/util/error.hpp" @@ -22,14 +22,15 @@ #include "src/util/logging.hpp" #include "src/util/types.hpp" -namespace fs = std::filesystem; -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::Option, util::types::None; +#include "include/matchit.hpp" namespace { + namespace fs = std::filesystem; + using std::chrono::system_clock, std::chrono::seconds, std::chrono::floor, std::chrono::duration_cast; + using util::cache::ReadCache, util::cache::WriteCache; + using util::error::DracError, util::error::DracErrorCode; + using util::types::Err, util::types::Exception, util::types::Result, util::types::String, util::types::u64, util::types::i64, util::types::Option; + fn GetCountFromDirectoryImpl( const String& pmId, const fs::path& dirPath, @@ -89,9 +90,7 @@ namespace { std::format("Filesystem error checking if '{}' is a directory: {}", dirPath.string(), fsErrCode.message()) )); - return Err( - DracError(DracErrorCode::NotFound, std::format("{} path is not a directory: {}", pmId, dirPath.string())) - ); + return Err(DracError(DracErrorCode::NotFound, std::format("{} path is not a directory: {}", pmId, dirPath.string()))); } fsErrCode.clear(); @@ -160,6 +159,9 @@ namespace { } // namespace namespace package { + namespace fs = std::filesystem; + using util::types::Err, util::types::None, util::types::Option, util::types::Result, util::types::String, util::types::u64; + fn GetCountFromDirectory( const String& pmId, const fs::path& dirPath, @@ -183,14 +185,17 @@ namespace package { } #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 + fn GetCountFromDb(const String& pmId, const fs::path& dbPath, const String& countQuery) -> Result { + using util::cache::ReadCache, util::cache::WriteCache; + using util::error::DracError, util::error::DracErrorCode; + using util::types::Exception, util::types::i64; + + const String cacheKey = "pkg_count_" + pmId; 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); + std::error_code errc; + const fs::file_time_type dbModTime = fs::last_write_time(dbPath, errc); if (errc) { warn_log( @@ -230,7 +235,7 @@ namespace package { } const SQLite::Database database(dbPath.string(), SQLite::OPEN_READONLY); - SQLite::Statement queryStmt(database, countQuery); // Use query directly + SQLite::Statement queryStmt(database, countQuery); if (queryStmt.executeStep()) { const i64 countInt64 = queryStmt.getColumn(0).getInt64(); @@ -268,20 +273,21 @@ namespace package { #endif // __serenity__ || _WIN32 #ifdef __linux__ - fn GetCountFromPlist(const String& pmId, const std::filesystem::path& plistPath) -> Result { - using namespace pugi; - using util::types::StringView; + fn GetCountFromPlist(const String& pmId, const fs::path& plistPath) -> Result { + using pugi::xml_document, pugi::xml_node, pugi::xml_parse_result; + using util::cache::ReadCache, util::cache::WriteCache; + using util::error::DracError, util::error::DracErrorCode; + using util::types::i64, util::types::StringView; const String cacheKey = "pkg_count_" + pmId; std::error_code fsErrCode; - // Cache check if (Result cachedDataResult = ReadCache(cacheKey)) { const auto& [cachedCount, timestamp] = *cachedDataResult; if (fs::exists(plistPath, fsErrCode) && !fsErrCode) { const fs::file_time_type plistModTime = fs::last_write_time(plistPath, fsErrCode); if (!fsErrCode) { - if (const std::chrono::system_clock::time_point cacheTimePoint = std::chrono::system_clock::time_point(std::chrono::seconds(timestamp)); + if (const system_clock::time_point cacheTimePoint = system_clock::time_point(seconds(timestamp)); cacheTimePoint.time_since_epoch() >= plistModTime.time_since_epoch()) { debug_log("Using valid {} plist count cache (file '{}' unchanged since {}). Count: {}", pmId, plistPath.string(), std::format("{:%F %T %Z}", std::chrono::floor(cacheTimePoint)), cachedCount); return cachedCount; @@ -296,17 +302,16 @@ namespace package { debug_log("{} plist count cache not found or unreadable", pmId); } - // Parse plist and count xml_document doc; xml_parse_result result = doc.load_file(plistPath.c_str()); if (!result) - return Err(util::error::DracError(util::error::DracErrorCode::ParseError, std::format("Failed to parse plist file '{}': {}", plistPath.string(), result.description()))); + return Err(DracError(DracErrorCode::ParseError, std::format("Failed to parse plist file '{}': {}", plistPath.string(), result.description()))); xml_node dict = doc.child("plist").child("dict"); if (!dict) - return Err(util::error::DracError(util::error::DracErrorCode::ParseError, std::format("No in plist file '{}'.", plistPath.string()))); + return Err(DracError(DracErrorCode::ParseError, std::format("No in plist file '{}'.", plistPath.string()))); u64 count = 0; @@ -340,7 +345,7 @@ namespace package { } if (count == 0) - return Err(util::error::DracError(util::error::DracErrorCode::NotFound, std::format("No installed packages found in plist file '{}'.", plistPath.string()))); + return Err(DracError(DracErrorCode::NotFound, std::format("No installed packages found in plist file '{}'.", plistPath.string()))); const i64 timestampEpochSeconds = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); const PkgCountCacheData dataToCache(count, timestampEpochSeconds); @@ -351,27 +356,13 @@ namespace package { #endif // __linux__ #if defined(__linux__) || defined(__APPLE__) - fn GetNixCount() -> Result { - const PackageManagerInfo nixInfo = { - .id = "nix", - .dbPath = "/nix/var/nix/db/db.sqlite", - .countQuery = "SELECT COUNT(path) FROM ValidPaths WHERE sigs IS NOT NULL", - }; - - if (std::error_code errc; !fs::exists(nixInfo.dbPath, errc)) { - if (errc) { - warn_log("Filesystem error checking for Nix DB at '{}': {}", nixInfo.dbPath.string(), errc.message()); - return Err(DracError(DracErrorCode::IoError, "Filesystem error checking Nix DB: " + errc.message())); - } - - return Err(DracError(DracErrorCode::ApiUnavailable, "Nix db not found: " + nixInfo.dbPath.string())); - } - - return GetCountFromDb(nixInfo); + fn CountNix() -> Result { + return GetCountFromDb("nix", "/nix/var/nix/db/db.sqlite", "SELECT COUNT(path) FROM ValidPaths WHERE sigs IS NOT NULL"); } #endif // __linux__ || __APPLE__ fn CountCargo() -> Result { + using util::error::DracError, util::error::DracErrorCode; using util::helpers::GetEnv; fs::path cargoPath {}; @@ -388,55 +379,87 @@ namespace package { } fn GetTotalCount() -> Result { - Vec>> futures; + using util::error::DracError; + using util::types::Array, util::types::Exception, util::types::Future; #ifdef __linux__ - // futures.push_back(std::async(std::launch::async, GetApkCount)); - futures.push_back(std::async(std::launch::async, GetDpkgCount)); - futures.push_back(std::async(std::launch::async, GetMossCount)); - futures.push_back(std::async(std::launch::async, GetPacmanCount)); - // futures.push_back(std::async(std::launch::async, GetPortageCount)); - futures.push_back(std::async(std::launch::async, GetRpmCount)); - futures.push_back(std::async(std::launch::async, GetXbpsCount)); - // futures.push_back(std::async(std::launch::async, GetZypperCount)); + constexpr size_t platformSpecificCount = 6; // Apk, Dpkg, Moss, Pacman, Rpm, Xbps #elifdef __APPLE__ - futures.push_back(std::async(std::launch::async, GetHomebrewCount)); - futures.push_back(std::async(std::launch::async, GetMacPortsCount)); + constexpr size_t platformSpecificCount = 2; // Homebrew, MacPorts #elifdef _WIN32 - 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)); + constexpr size_t platformSpecificCount = 3; // WinGet, Chocolatey, Scoop #elif defined(__FreeBSD__) || defined(__DragonFly__) - futures.push_back(std::async(std::launch::async, GetPkgNgCount)); + constexpr size_t platformSpecificCount = 1; // GetPkgNgCount #elifdef __NetBSD__ - futures.push_back(std::async(std::launch::async, GetPkgSrcCount)); + constexpr size_t platformSpecificCount = 1; // GetPkgSrcCount #elifdef __HAIKU__ - futures.push_back(std::async(std::launch::async, GetHaikuCount)); + constexpr size_t platformSpecificCount = 1; // GetHaikuCount #elifdef __serenity__ - futures.push_back(std::async(std::launch::async, GetSerenityCount)); + constexpr size_t platformSpecificCount = 1; // GetSerenityCount #endif #if defined(__linux__) || defined(__APPLE__) - futures.push_back(std::async(std::launch::async, GetNixCount)); + // platform specific + cargo + nix + constexpr size_t numFutures = platformSpecificCount + 2; +#else + // platform specific + cargo + constexpr size_t numFutures = platformSpecificCount + 1; #endif - futures.push_back(std::async(std::launch::async, CountCargo)); + + Array>, numFutures> + futures = { + { +#ifdef __linux__ + std::async(std::launch::async, CountApk), + std::async(std::launch::async, CountDpkg), + std::async(std::launch::async, CountMoss), + std::async(std::launch::async, CountPacman), + std::async(std::launch::async, CountRpm), + std::async(std::launch::async, CountXbps), + // std::async(std::launch::async, CountZypper), +#elifdef __APPLE__ + std::async(std::launch::async, GetHomebrewCount), + std::async(std::launch::async, GetMacPortsCount), +#elifdef _WIN32 + std::async(std::launch::async, CountWinGet), + std::async(std::launch::async, CountChocolatey), + std::async(std::launch::async, CountScoop), +#elif defined(__FreeBSD__) || defined(__DragonFly__) + std::async(std::launch::async, GetPkgNgCount), +#elifdef __NetBSD__ + std::async(std::launch::async, GetPkgSrcCount), +#elifdef __HAIKU__ + std::async(std::launch::async, GetHaikuCount), +#elifdef __serenity__ + std::async(std::launch::async, GetSerenityCount), +#endif + +#if defined(__linux__) || defined(__APPLE__) + std::async(std::launch::async, CountNix), +#endif + + std::async(std::launch::async, CountCargo), + } + }; u64 totalCount = 0; bool oneSucceeded = false; for (Future>& fut : futures) { try { + using matchit::match, matchit::is, matchit::or_, matchit::_; 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 != NotFound && result.error().code != ApiUnavailable && - result.error().code != NotSupported) { - error_at(result.error()); - } else - debug_at(result.error()); + } else { + match(result.error().code)( + is | or_(NotFound, ApiUnavailable, NotSupported) = [&] -> void { debug_at(result.error()); }, + is | _ = [&] -> void { error_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 e7ebd3e..d43aabd 100644 --- a/src/core/package.hpp +++ b/src/core/package.hpp @@ -56,7 +56,7 @@ namespace package { * @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 String& pmId, const fs::path& dbPath, const String& countQuery) -> Result; /** * @brief Gets package count by iterating entries in a directory, optionally filtering and subtracting. @@ -101,14 +101,12 @@ namespace package { fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath) -> Result; #ifdef __linux__ - fn GetApkCount() -> Result; - fn GetDpkgCount() -> Result; - fn GetMossCount() -> Result; - fn GetPacmanCount() -> Result; - // fn GetPortageCount() -> Result; - fn GetRpmCount() -> Result; - fn GetXbpsCount() -> Result; - // fn GetZypperCount() -> Result; + fn CountApk() -> Result; + fn CountDpkg() -> Result; + fn CountMoss() -> Result; + fn CountPacman() -> Result; + fn CountRpm() -> Result; + fn CountXbps() -> Result; /** * @brief Counts installed packages in a plist file (used by xbps and potentially others). @@ -136,7 +134,7 @@ namespace package { // Common (potentially cross-platform) #ifndef _WIN32 - fn GetNixCount() -> Result; + fn CountNix() -> Result; #endif fn CountCargo() -> Result; } // namespace package diff --git a/src/main.cpp b/src/main.cpp index e97f1ad..a606213 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include +#include // EXIT_FAILURE, EXIT_SUCCESS #include // ftxui::{Element, hbox, vbox, text, separator, filler, etc.} #include // ftxui::{Render} #include // ftxui::{Screen, Dimension::Full} diff --git a/src/os/linux.cpp b/src/os/linux.cpp index c57f210..06a4783 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -26,6 +26,7 @@ #include // std::move #include "src/core/package.hpp" +#include "src/util/cache.hpp" #include "src/util/defs.hpp" #include "src/util/error.hpp" #include "src/util/helpers.hpp" @@ -494,18 +495,97 @@ namespace os { namespace package { using namespace std::string_literals; - fn GetDpkgCount() -> Result { + fn CountApk() -> Result { + using namespace util::cache; + + const String pmId = "apk"; + const fs::path apkDbPath = "/lib/apk/db/installed"; + const String cacheKey = "pkg_count_" + pmId; + + std::error_code fsErrCode; + + if (!fs::exists(apkDbPath, fsErrCode)) { + if (fsErrCode) { + warn_log("Filesystem error checking for Apk DB at '{}': {}", apkDbPath.string(), fsErrCode.message()); + return Err(DracError(DracErrorCode::IoError, "Filesystem error checking Apk DB: " + fsErrCode.message())); + } + + return Err(DracError(DracErrorCode::NotFound, std::format("Apk database path '{}' does not exist", apkDbPath.string()))); + } + + if (Result cachedDataResult = ReadCache(cacheKey)) { + const auto& [cachedCount, timestamp] = *cachedDataResult; + std::error_code modTimeErrCode; + const fs::file_time_type dbModTime = fs::last_write_time(apkDbPath, modTimeErrCode); + + if (modTimeErrCode) { + warn_log( + "Could not get modification time for '{}': {}. Invalidating {} cache.", + apkDbPath.string(), + modTimeErrCode.message(), + pmId + ); + } else { + using std::chrono::system_clock, std::chrono::seconds, std::chrono::floor; + const system_clock::time_point cacheTimePoint = system_clock::time_point(seconds(timestamp)); + + if (cacheTimePoint.time_since_epoch() >= dbModTime.time_since_epoch()) { + debug_log("Using valid {} package count cache (DB file unchanged since {}). Count: {}", pmId, std::format("{:%F %T %Z}", floor(cacheTimePoint)), cachedCount); + return cachedCount; + } + + 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 file: {}", pmId, apkDbPath.string()); + + std::ifstream file(apkDbPath); + if (!file.is_open()) + return Err(DracError(DracErrorCode::IoError, std::format("Failed to open Apk database file '{}'", apkDbPath.string()))); + + String line; + + u64 count = 0; + + try { + while (std::getline(file, line)) + if (line.empty()) + count++; + } catch (const std::ios_base::failure& e) { + return Err(DracError( + DracErrorCode::IoError, + std::format("Error reading Apk database file '{}': {}", apkDbPath.string(), e.what()) + )); + } + + if (file.bad()) + return Err(DracError(DracErrorCode::IoError, std::format("IO error while reading Apk database file '{}'", apkDbPath.string()))); + + { + using std::chrono::duration_cast, std::chrono::system_clock, std::chrono::seconds; + + const i64 timestampEpochSeconds = duration_cast(system_clock::now().time_since_epoch()).count(); + + const PkgCountCacheData dataToCache(count, timestampEpochSeconds); + + if (Result writeResult = WriteCache(cacheKey, dataToCache); !writeResult) + debug_at(writeResult.error()); + } + + return count; + } + + fn CountDpkg() -> Result { return GetCountFromDirectory("Dpkg", fs::current_path().root_path() / "var" / "lib" / "dpkg" / "info", ".list"s); } - fn GetMossCount() -> Result { - const PackageManagerInfo mossInfo = { - .id = "moss", - .dbPath = "/.moss/db/install", - .countQuery = "SELECT COUNT(*) FROM meta", - }; - - Result countResult = GetCountFromDb(mossInfo); + fn CountMoss() -> Result { + Result countResult = GetCountFromDb("moss", "/.moss/db/install", "SELECT COUNT(*) FROM meta"); if (countResult) if (*countResult > 0) @@ -514,21 +594,15 @@ namespace package { return countResult; } - fn GetPacmanCount() -> Result { + fn CountPacman() -> Result { return GetCountFromDirectory("Pacman", fs::current_path().root_path() / "var" / "lib" / "pacman" / "local", true); } - fn GetRpmCount() -> Result { - const PackageManagerInfo rpmInfo = { - .id = "rpm", - .dbPath = "/var/lib/rpm/rpmdb.sqlite", - .countQuery = "SELECT COUNT(*) FROM Installtid", - }; - - return GetCountFromDb(rpmInfo); + fn CountRpm() -> Result { + return GetCountFromDb("rpm", "/var/lib/rpm/rpmdb.sqlite", "SELECT COUNT(*) FROM Installtid"); } - fn GetXbpsCount() -> Result { + fn CountXbps() -> Result { const StringView xbpsDbPath = "/var/db/xbps"; const String pmId = "xbps"; diff --git a/src/os/os.hpp b/src/os/os.hpp index a45772d..86998ff 100644 --- a/src/os/os.hpp +++ b/src/os/os.hpp @@ -14,9 +14,7 @@ * (found in linux.cpp, windows.cpp, macos.cpp). */ namespace os { - using util::error::DracError; - using util::types::u64, util::types::String, util::types::Option, util::types::Result, util::types::MediaInfo, - util::types::DiskSpace; + using util::types::u64, util::types::String, util::types::Result, util::types::MediaInfo, util::types::DiskSpace; /** * @brief Get the total amount of physical RAM installed in the system. diff --git a/src/wrappers/wayland.hpp b/src/wrappers/wayland.hpp index 9bc8317..ed06709 100644 --- a/src/wrappers/wayland.hpp +++ b/src/wrappers/wayland.hpp @@ -15,17 +15,15 @@ struct wl_display; namespace wl { using display = wl_display; - // NOLINTBEGIN(readability-identifier-naming) - inline fn connect(const char* name) -> display* { + inline fn Connect(const char* name) -> display* { return wl_display_connect(name); } - inline fn disconnect(display* display) -> void { + inline fn Disconnect(display* display) -> void { wl_display_disconnect(display); } - inline fn get_fd(display* display) -> int { + inline fn GetFd(display* display) -> int { return wl_display_get_fd(display); } - // NOLINTEND(readability-identifier-naming) /** * RAII wrapper for Wayland display connections @@ -70,12 +68,12 @@ namespace wl { }); // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) - needs to come after wl_log_set_handler_client - m_display = connect(nullptr); + m_display = Connect(nullptr); } ~DisplayGuard() { if (m_display) - disconnect(m_display); + Disconnect(m_display); } // Non-copyable @@ -88,7 +86,7 @@ namespace wl { fn operator=(DisplayGuard&& other) noexcept -> DisplayGuard& { if (this != &other) { if (m_display) - disconnect(m_display); + Disconnect(m_display); m_display = std::exchange(other.m_display, nullptr); } @@ -104,7 +102,7 @@ namespace wl { return m_display; } [[nodiscard]] fn fd() const -> util::types::i32 { - return get_fd(m_display); + return GetFd(m_display); } }; } // namespace wl