unfinished but im tired

This commit is contained in:
Mars 2025-05-04 02:40:26 -04:00
parent 71f9c1ce63
commit d693c8cfb1
Signed by: pupbrained
GPG key ID: 0FF5B8826803F895
13 changed files with 457 additions and 531 deletions

View file

@ -88,7 +88,7 @@ add_project_arguments(common_cpp_args, language : 'cpp')
# ------- # # ------- #
# Files # # Files #
# ------- # # ------- #
base_sources = files('src/core/system_data.cpp', 'src/config/config.cpp', 'src/config/weather.cpp', 'src/main.cpp') base_sources = files('src/core/system_data.cpp', 'src/core/package.cpp', 'src/config/config.cpp', 'src/config/weather.cpp', 'src/main.cpp')
platform_sources = { platform_sources = {
'darwin' : ['src/os/macos.cpp', 'src/os/macos/bridge.mm'], 'darwin' : ['src/os/macos.cpp', 'src/os/macos/bridge.mm'],

335
src/core/package.cpp Normal file
View file

@ -0,0 +1,335 @@
#include "package.hpp"
#include <SQLiteCpp/Database.h> // SQLite::{Database, OPEN_READONLY}
#include <SQLiteCpp/Exception.h> // SQLite::Exception
#include <SQLiteCpp/Statement.h> // SQLite::Statement
#include <chrono> // std::chrono
#include <filesystem> // std::filesystem
#include <format> // std::format
#include <future> // std::{async, future, launch}
#include <system_error> // std::error_code
#include "src/util/cache.hpp"
#include "src/util/error.hpp"
#include "src/util/helpers.hpp"
#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;
namespace {
fn GetCountFromDirectoryImpl(
const String& pmId,
const fs::path& dirPath,
const String& fileExtensionFilter,
const bool subtractOne
) -> Result<u64, DracError> {
debug_log("Counting packages for '{}' in directory: {}", pmId, dirPath.string());
std::error_code errc;
if (!fs::exists(dirPath, errc)) {
if (errc)
warn_log("Filesystem error checking {} directory '{}': {}", pmId, dirPath.string(), errc.message());
return Err(DracError(DracErrorCode::NotFound, std::format("{} directory not found: {}", pmId, dirPath.string())));
}
errc.clear();
if (!fs::is_directory(dirPath, errc)) {
if (errc)
return Err(DracError(
DracErrorCode::IoError,
std::format("Filesystem error checking if '{}' is a directory: {}", dirPath.string(), errc.message())
));
return Err(
DracError(DracErrorCode::IoError, std::format("{} path is not a directory: {}", pmId, dirPath.string()))
);
}
errc.clear();
u64 count = 0;
bool filterActive = !fileExtensionFilter.empty();
try {
const fs::directory_iterator dirIter(dirPath, fs::directory_options::skip_permission_denied, errc);
if (errc) {
return Err(DracError(
DracErrorCode::IoError,
std::format("Failed to create iterator for {} directory '{}': {}", pmId, dirPath.string(), errc.message())
));
}
errc.clear();
for (const fs::directory_entry& entry : dirIter) {
if (entry.path().empty())
continue;
std::error_code entryStatErr;
bool isFile = false;
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();
continue;
}
}
if (filterActive) {
if (isFile && entry.path().extension().string() == fileExtensionFilter)
count++;
} else
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 (...) {
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());
return count;
}
} // namespace
namespace package {
fn GetCountFromDirectory(
const String& pmId,
const fs::path& dirPath,
const String& fileExtensionFilter,
const bool subtractOne
) -> Result<u64, DracError> {
return GetCountFromDirectoryImpl(pmId, dirPath, fileExtensionFilter, subtractOne);
}
fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath, const String& fileExtensionFilter)
-> Result<u64, DracError> {
return GetCountFromDirectoryImpl(pmId, dirPath, fileExtensionFilter, false);
}
fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath, const bool subtractOne)
-> Result<u64, DracError> {
const String noFilter;
return GetCountFromDirectoryImpl(pmId, dirPath, noFilter, subtractOne);
}
fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath) -> Result<u64, DracError> {
const String noFilter;
return GetCountFromDirectoryImpl(pmId, dirPath, noFilter, false);
}
fn GetCountFromDb(const PackageManagerInfo& pmInfo) -> Result<u64, DracError> {
const auto& [pmId, dbPath, countQuery] = pmInfo;
const String cacheKey = "pkg_count_" + pmId; // More specific cache key
if (Result<PkgCountCacheData, DracError> cachedDataResult = ReadCache<PkgCountCacheData>(cacheKey)) {
const auto& [count, timestamp] = *cachedDataResult;
std::error_code errc;
const std::filesystem::file_time_type dbModTime = fs::last_write_time(dbPath, errc);
if (errc) {
warn_log(
"Could not get modification time for '{}': {}. Invalidating {} cache.", dbPath.string(), errc.message(), pmId
);
} else {
if (const system_clock::time_point cacheTimePoint = system_clock::time_point(seconds(timestamp));
cacheTimePoint.time_since_epoch() >= dbModTime.time_since_epoch()) {
debug_log(
"Using valid {} package count cache (DB file unchanged since {}). Count: {}",
pmId,
std::format("{:%F %T %Z}", floor<seconds>(cacheTimePoint)),
count
);
return count;
}
debug_log("{} package count cache stale (DB file modified).", pmId);
}
} else {
if (cachedDataResult.error().code != DracErrorCode::NotFound)
debug_at(cachedDataResult.error());
debug_log("{} package count cache not found or unreadable.", pmId);
}
debug_log("Fetching fresh {} package count from database: {}", pmId, dbPath.string());
u64 count = 0;
try {
// Ensure database file exists before trying to open
std::error_code existsErr;
if (!fs::exists(dbPath, existsErr) || existsErr) {
if (existsErr) {
warn_log("Error checking existence of {} DB '{}': {}", pmId, dbPath.string(), existsErr.message());
}
return Err(
DracError(DracErrorCode::NotFound, std::format("{} database not found at '{}'", pmId, dbPath.string()))
);
}
const SQLite::Database database(dbPath.string(), SQLite::OPEN_READONLY);
SQLite::Statement queryStmt(database, countQuery); // Use query directly
if (queryStmt.executeStep()) {
const i64 countInt64 = queryStmt.getColumn(0).getInt64();
if (countInt64 < 0)
return Err(
DracError(DracErrorCode::ParseError, std::format("Negative count returned by {} DB COUNT query.", pmId))
);
count = static_cast<u64>(countInt64);
} else {
// 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)));
}
} 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())
));
} catch (const Exception& e) {
error_log("Standard exception accessing {} DB '{}': {}", pmId, dbPath.string(), e.what());
return Err(DracError(DracErrorCode::InternalError, e.what()));
} catch (...) {
error_log("Unknown error occurred accessing {} DB '{}'", pmId, dbPath.string());
return Err(DracError(DracErrorCode::Other, std::format("Unknown error occurred accessing {} DB", pmId)));
}
debug_log("Successfully fetched {} package count: {}.", pmId, count);
const i64 nowEpochSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
const PkgCountCacheData dataToCache = { .count = count, .timestampEpochSeconds = nowEpochSeconds };
if (Result<void, DracError> writeResult = WriteCache(cacheKey, dataToCache); !writeResult)
error_at(writeResult.error()); // Log cache write error but return the count anyway
return count;
}
#if defined(__linux__) || defined(__APPLE__)
fn GetNixCount() -> Result<u64, DracError> {
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);
}
#endif
fn GetCargoCount() -> Result<u64, DracError> {
using util::helpers::GetEnv;
fs::path cargoPath {};
if (const Result<String, DracError> cargoHome = GetEnv("CARGO_HOME"))
cargoPath = fs::path(*cargoHome) / "bin";
else if (const Result<String, DracError> homeDir = GetEnv("HOME"))
cargoPath = fs::path(*homeDir) / ".cargo" / "bin";
if (cargoPath.empty() || !fs::exists(cargoPath))
return Err(DracError(DracErrorCode::NotFound, "Could not find cargo directory"));
u64 count = 0;
for (const fs::directory_entry& entry : fs::directory_iterator(cargoPath))
if (entry.is_regular_file())
++count;
debug_log("Found {} packages in cargo directory: {}", count, cargoPath.string());
return count;
}
fn GetTotalCount() -> Result<u64, DracError> {
Vec<Future<Result<u64, DracError>>> futures;
#ifdef __linux__
futures.push_back(std::async(std::launch::async, GetDpkgCount));
futures.push_back(std::async(std::launch::async, GetPacmanCount));
// futures.push_back(std::async(std::launch::async, GetRpmCount));
// futures.push_back(std::async(std::launch::async, GetPortageCount));
// futures.push_back(std::async(std::launch::async, GetZypperCount));
// futures.push_back(std::async(std::launch::async, GetApkCount));
futures.push_back(std::async(std::launch::async, GetMossCount));
#elifdef __APPLE__
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));
#elif defined(__FreeBSD__) || defined(__DragonFly__)
futures.push_back(std::async(std::launch::async, GetPkgNgCount));
#elifdef __NetBSD__
futures.push_back(std::async(std::launch::async, GetPkgSrcCount));
#elifdef __HAIKU__
futures.push_back(std::async(std::launch::async, GetHaikuCount));
#elifdef __serenity__
futures.push_back(std::async(std::launch::async, GetSerenityCount));
#endif
#if defined(__linux__) || defined(__APPLE__)
futures.push_back(std::async(std::launch::async, GetNixCount));
#endif
futures.push_back(std::async(std::launch::async, GetCargoCount));
u64 totalCount = 0;
bool oneSucceeded = false;
for (Future<Result<u64, DracError>>& fut : futures) {
try {
if (Result<u64, DracError> 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());
} catch (...) { error_log("Caught unknown exception while getting package count future."); }
}
if (!oneSucceeded && totalCount == 0)
return Err(DracError(DracErrorCode::NotFound, "No package managers found or none reported counts."));
debug_log("Final total package count: {}", totalCount);
return totalCount;
}
} // namespace package

View file

@ -1,34 +1,35 @@
#pragma once #pragma once
#include <filesystem> // std::filesystem::path #include <filesystem> // std::filesystem::path
#include <future> // std::future
#include <glaze/core/common.hpp> // glz::object #include <glaze/core/common.hpp> // glz::object
#include <glaze/core/meta.hpp> // glz::detail::Object #include <glaze/core/meta.hpp> // glz::detail::Object
// Include necessary type headers used in declarations
#include "src/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/util/error.hpp" #include "src/util/error.hpp"
#include "src/util/types.hpp" #include "src/util/types.hpp" // Brings in Result, u64, etc.
namespace packages { namespace package {
namespace fs = std::filesystem; namespace fs = std::filesystem;
using util::error::DracError; using util::error::DracError;
using util::types::Result, util::types::u64, util::types::i64, util::types::String, util::types::Vec, using util::types::Future;
util::types::Future; using util::types::i64;
using util::types::Result;
using util::types::String;
using util::types::u64;
/** /**
* @struct PkgCountCacheData * @struct PkgCountCacheData
* @brief Structure for caching package count results along with a timestamp. * @brief Structure for caching package count results along with a timestamp.
*
* Used to avoid redundant lookups in package manager databases or directories
* if the underlying data source hasn't changed recently.
*/ */
struct PkgCountCacheData { struct PkgCountCacheData {
u64 count {}; ///< The cached package count. u64 count {};
i64 timestampEpochSeconds {}; ///< The UNIX timestamp (seconds since epoch) when the count was cached. i64 timestampEpochSeconds {};
// NOLINTBEGIN(readability-identifier-naming) // NOLINTBEGIN(readability-identifier-naming)
struct [[maybe_unused]] glaze { struct [[maybe_unused]] glaze {
using T = PkgCountCacheData; using T = PkgCountCacheData;
static constexpr glz::detail::Object value = static constexpr glz::detail::Object value =
glz::object("count", &T::count, "timestamp", &T::timestampEpochSeconds); glz::object("count", &T::count, "timestamp", &T::timestampEpochSeconds);
}; };
@ -40,31 +41,67 @@ namespace packages {
* @brief Holds information needed to query a database-backed package manager. * @brief Holds information needed to query a database-backed package manager.
*/ */
struct PackageManagerInfo { struct PackageManagerInfo {
String id; ///< Unique identifier for the package manager (used for cache key). String id; ///< Unique identifier (e.g., "pacman", "dpkg", used for cache key).
fs::path dbPath; ///< Filesystem path to the package manager's database. fs::path dbPath; ///< Filesystem path to the database or primary directory.
String countQuery; ///< SQL query string to count the packages. String countQuery; ///< Query string (e.g., SQL) or specific file/pattern if not DB.
}; };
/** /**
* @brief Gets the total package count by querying all relevant package managers for the current OS. * @brief Gets the total package count by querying all relevant package managers.
* @details This function orchestrates the process:
* 1. Determines the set of relevant package managers (platform-specific + shared).
* 2. Launches asynchronous tasks to query each manager.
* 3. Aggregates the results, summing counts and logging errors.
* @return Result containing the total package count (u64) on success, * @return Result containing the total package count (u64) on success,
* or a DracError if the aggregation fails (though individual manager errors are logged). * or a DracError if aggregation fails (individual errors logged).
*/ */
fn GetTotalCount() -> Result<u64, DracError>; fn GetTotalCount() -> Result<u64, DracError>;
/**
* @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<u64, DracError>; fn GetCountFromDb(const PackageManagerInfo& pmInfo) -> Result<u64, DracError>;
/**
* @brief Gets package count by iterating entries in a directory, optionally filtering and subtracting.
* @param pmId Identifier for the package manager (for logging/cache).
* @param dirPath Path to the directory to iterate.
* @param fileExtensionFilter Only count files with this extension (e.g., ".list").
* @param subtractOne Subtract one from the final count.
* @return Result containing the count (u64) or a DracError.
*/
fn GetCountFromDirectory( fn GetCountFromDirectory(
const String& pmId, const String& pmId,
const fs::path& dirPath, const fs::path& dirPath,
const String& fileExtensionFilter = "", const String& fileExtensionFilter,
bool subtractOne = false bool subtractOne
) -> Result<u64, DracError>; ) -> Result<u64, DracError>;
/**
* @brief Gets package count by iterating entries in a directory, filtering by extension.
* @param pmId Identifier for the package manager (for logging/cache).
* @param dirPath Path to the directory to iterate.
* @param fileExtensionFilter Only count files with this extension (e.g., ".list").
* @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<u64, DracError>;
/**
* @brief Gets package count by iterating entries in a directory, optionally subtracting one.
* @param pmId Identifier for the package manager (for logging/cache).
* @param dirPath Path to the directory to iterate.
* @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<u64, DracError>;
/**
* @brief Gets package count by iterating all entries in a directory.
* @param pmId Identifier for the package manager (for logging/cache).
* @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<u64, DracError>;
#ifdef __linux__ #ifdef __linux__
fn GetDpkgCount() -> Result<u64, DracError>; fn GetDpkgCount() -> Result<u64, DracError>;
fn GetPacmanCount() -> Result<u64, DracError>; fn GetPacmanCount() -> Result<u64, DracError>;
@ -73,17 +110,26 @@ namespace packages {
fn GetZypperCount() -> Result<u64, DracError>; fn GetZypperCount() -> Result<u64, DracError>;
fn GetPortageCount() -> Result<u64, DracError>; fn GetPortageCount() -> Result<u64, DracError>;
fn GetApkCount() -> Result<u64, DracError>; fn GetApkCount() -> Result<u64, DracError>;
#elif defined(__APPLE__) #elifdef __APPLE__
fn GetHomebrewCount() -> Result<u64, DracError>; fn GetHomebrewCount() -> Result<u64, DracError>;
fn GetMacPortsCount() -> Result<u64, DracError>; fn GetMacPortsCount() -> Result<u64, DracError>;
#elif defined(_WIN32) #elifdef _WIN32
fn GetWinRTCount() -> Result<u64, DracError>; fn GetWinRTCount() -> Result<u64, DracError>;
fn GetChocolateyCount() -> Result<u64, DracError>; fn GetChocolateyCount() -> Result<u64, DracError>;
fn GetScoopCount() -> Result<u64, DracError>; fn GetScoopCount() -> Result<u64, DracError>;
#elif defined(__FreeBSD__) || defined(__DragonFly__)
fn GetPkgNgCount() -> Result<u64, DracError>;
#elifdef __NetBSD__
fn GetPkgSrcCount() -> Result<u64, DracError>;
#elifdef __HAIKU__
fn GetHaikuCount() -> Result<u64, DracError>;
#elifdef __serenity__
fn GetSerenityCount() -> Result<u64, DracError>;
#endif #endif
// Common (potentially cross-platform)
#ifndef _WIN32 #ifndef _WIN32
fn GetNixCount() -> Result<u64, DracError>; fn GetNixCount() -> Result<u64, DracError>;
#endif #endif
fn GetCargoCount() -> Result<u64, DracError>; fn GetCargoCount() -> Result<u64, DracError>;
} // namespace packages } // namespace package

View file

@ -7,6 +7,7 @@
#include "src/config/config.hpp" #include "src/config/config.hpp"
#include "src/config/weather.hpp" #include "src/config/weather.hpp"
#include "src/core/package.hpp"
#include "src/os/os.hpp" #include "src/os/os.hpp"
#include "src/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/util/error.hpp" #include "src/util/error.hpp"
@ -78,7 +79,7 @@ namespace os {
Future<Result<String, DracError>> wmFut = std::async(async, GetWindowManager); Future<Result<String, DracError>> wmFut = std::async(async, GetWindowManager);
Future<Result<DiskSpace, DracError>> diskFut = std::async(async, GetDiskUsage); Future<Result<DiskSpace, DracError>> diskFut = std::async(async, GetDiskUsage);
Future<Result<String, DracError>> shellFut = std::async(async, GetShell); Future<Result<String, DracError>> shellFut = std::async(async, GetShell);
Future<Result<u64, DracError>> pkgFut = std::async(async, GetPackageCount); Future<Result<u64, DracError>> pkgFut = std::async(async, package::GetTotalCount);
Future<Result<MediaInfo, DracError>> npFut = Future<Result<MediaInfo, DracError>> npFut =
std::async(config.nowPlaying.enabled ? async : deferred, GetNowPlaying); std::async(config.nowPlaying.enabled ? async : deferred, GetNowPlaying);
Future<Result<Output, DracError>> wthrFut = Future<Result<Output, DracError>> wthrFut =

View file

@ -26,155 +26,22 @@ using util::error::DracError, util::error::DracErrorCode;
using util::types::u64, util::types::i64, util::types::Result, util::types::Err, util::types::String, using util::types::u64, util::types::i64, util::types::Result, util::types::Err, util::types::String,
util::types::StringView, util::types::Exception; util::types::StringView, util::types::Exception;
namespace { namespace package {
using namespace std::chrono; #if defined(__FreeBSD__) || defined(__DragonFly__)
using namespace util::cache; fn GetPkgNgCount() -> Result<u64, DracError> {
using os::bsd::PackageManagerInfo, os::bsd::PkgCountCacheData;
#ifdef __NetBSD__
fn GetPackageCountInternalDir(const String& pmId, const fs::path& dirPath) -> Result<u64, DracError> {
debug_log("Attempting to get {} package count.", pmId);
std::error_code errc;
if (!fs::exists(dirPath, errc)) {
if (errc)
return Err(DracError(
DracErrorCode::IoError, std::format("Filesystem error checking {} directory: {}", pmId, errc.message())
));
return Err(
DracError(DracErrorCode::ApiUnavailable, std::format("{} directory not found: {}", pmId, dirPath.string()))
);
}
if (!fs::is_directory(dirPath, errc)) {
if (errc)
return Err(DracError(
DracErrorCode::IoError, std::format("Filesystem error checking {} path type: {}", pmId, errc.message())
));
warn_log("Expected {} directory at '{}', but it's not a directory.", pmId, dirPath.string());
return Err(
DracError(DracErrorCode::IoError, std::format("{} path is not a directory: {}", pmId, dirPath.string()))
);
}
u64 count = 0;
try {
const fs::directory_iterator dirIter(dirPath, fs::directory_options::skip_permission_denied, errc);
if (errc)
return Err(
DracError(DracErrorCode::IoError, std::format("Failed to iterate {} directory: {}", pmId, errc.message()))
);
for (const fs::directory_entry& entry : dirIter) { count++; }
} catch (const fs::filesystem_error& e) {
return Err(DracError(
DracErrorCode::IoError,
std::format("Filesystem error iterating {} directory '{}': {}", pmId, dirPath.string(), e.what())
));
} catch (...) {
return Err(DracError(
DracErrorCode::Other, std::format("Unknown error iterating {} directory '{}'", pmId, dirPath.string())
));
}
if (count > 0)
count--;
return count;
}
#else
fn GetPackageCountInternalDb(const PackageManagerInfo& pmInfo) -> Result<u64, DracError> {
const auto& [pmId, dbPath, countQuery] = pmInfo;
if (Result<PkgCountCacheData, DracError> cachedDataResult = ReadCache<PkgCountCacheData>(pmId)) {
const auto& [count, timestamp] = *cachedDataResult;
std::error_code errc;
const std::filesystem::file_time_type dbModTime = fs::last_write_time(dbPath, errc);
if (errc) {
warn_log(
"Could not get modification time for '{}': {}. Invalidating {} cache.", dbPath.string(), errc.message(), pmId
);
} else {
if (const system_clock::time_point cacheTimePoint = system_clock::time_point(seconds(timestamp));
cacheTimePoint.time_since_epoch() >= dbModTime.time_since_epoch()) {
debug_log(
"Using valid {} package count cache (DB file unchanged since {}).",
pmId,
std::format("{:%F %T %Z}", floor<seconds>(cacheTimePoint))
);
return count;
}
debug_log("{} package count cache stale (DB file modified).", pmId);
}
} else {
if (cachedDataResult.error().code != DracErrorCode::NotFound)
debug_at(cachedDataResult.error());
debug_log("{} package count cache not found or unreadable.", pmId);
}
debug_log("Fetching fresh {} package count from database: {}", pmId, dbPath.string());
u64 count = 0;
try {
const SQLite::Database database(dbPath.string(), SQLite::OPEN_READONLY);
if (SQLite::Statement query(database, countQuery); query.executeStep()) {
const i64 countInt64 = query.getColumn(0).getInt64();
if (countInt64 < 0)
return Err(
DracError(DracErrorCode::ParseError, std::format("Negative count returned by {} DB COUNT query.", pmId))
);
count = static_cast<u64>(countInt64);
} else {
return Err(DracError(DracErrorCode::ParseError, std::format("No rows returned by {} DB COUNT query.", pmId)));
}
} catch (const SQLite::Exception& e) {
return Err(DracError(
DracErrorCode::ApiUnavailable, std::format("SQLite error occurred accessing {} DB: {}", pmId, e.what())
));
} catch (const Exception& e) { return Err(DracError(DracErrorCode::InternalError, e.what())); } catch (...) {
return Err(DracError(DracErrorCode::Other, std::format("Unknown error occurred accessing {} DB", pmId)));
}
const i64 nowEpochSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
const PkgCountCacheData dataToCache = { .count = count, .timestampEpochSeconds = nowEpochSeconds };
if (Result<void, DracError> writeResult = WriteCache(pmId, dataToCache); !writeResult)
error_at(writeResult.error());
return count;
}
#endif
} // namespace
namespace os {
fn GetPackageCount() -> Result<u64, DracError> {
#ifdef __NetBSD__
return GetPackageCountInternalDir("PkgSrc", fs::current_path().root_path() / "usr" / "pkg" / "pkgdb");
#else
const PackageManagerInfo pkgInfo = { const PackageManagerInfo pkgInfo = {
.id = "pkg_count", .id = "pkgng", // Use core struct
.dbPath = "/var/db/pkg/local.sqlite", .dbPath = "/var/db/pkg/local.sqlite",
.countQuery = "SELECT COUNT(*) FROM packages", .countQuery = "SELECT COUNT(*) FROM packages",
}; };
if (std::error_code errc; !fs::exists(pkgInfo.dbPath, errc)) { return GetCountFromDb(pkgInfo);
if (errc) {
warn_log("Filesystem error checking for pkg DB at '{}': {}", pkgInfo.dbPath.string(), errc.message());
return Err(DracError(DracErrorCode::IoError, "Filesystem error checking Nix DB: " + errc.message()));
} }
#elif defined(__NetBSD__)
return Err(DracError(DracErrorCode::ApiUnavailable, "pkg db not found: " + pkgInfo.dbPath.string())); fn GetPkgSrcCount() -> Result<u64, DracError> {
return GetCountFromDirectory("pkgsrc", fs::current_path().root_path() / "usr" / "pkg" / "pkgdb", true);
} }
return GetPackageCountInternalDb(pkgInfo);
#endif #endif
} } // namespace package
} // namespace os
#endif // __FreeBSD__ || __DragonFly__ || __NetBSD__ #endif // __FreeBSD__ || __DragonFly__ || __NetBSD__

View file

@ -1,36 +0,0 @@
#pragma once
// clang-format off
#include <filesystem> // std::filesystem::path
#include <glaze/core/common.hpp> // glz::object
#include <glaze/core/meta.hpp> // glz::detail::Object
#include "src/util/error.hpp"
#include "src/util/types.hpp"
// clang-format on
namespace os::bsd {
using util::error::DracError;
using util::types::Result, util::types::u64, util::types::i64, util::types::String;
namespace fs = std::filesystem;
struct PackageManagerInfo {
String id;
fs::path dbPath;
String countQuery;
};
struct PkgCountCacheData {
u64 count {};
i64 timestampEpochSeconds {};
// NOLINTBEGIN(readability-identifier-naming)
struct [[maybe_unused]] glaze {
using T = PkgCountCacheData;
static constexpr glz::detail::Object value =
glz::object("count", &T::count, "timestamp", &T::timestampEpochSeconds);
};
// NOLINTEND(readability-identifier-naming)
};
} // namespace os::bsd

View file

@ -259,18 +259,6 @@ namespace os {
.total_bytes = stat.f_blocks * stat.f_frsize, .total_bytes = stat.f_blocks * stat.f_frsize,
}; };
} }
fn GetPackageCount() -> Result<u64, DracError> {
BPackageKit::BPackageRoster roster;
BPackageKit::BPackageInfoSet packageList;
const status_t status = roster.GetActivePackages(BPackageKit::B_PACKAGE_INSTALLATION_LOCATION_SYSTEM, packageList);
if (status != B_OK)
return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to get active package list"));
return static_cast<u64>(packageList.CountInfos());
}
} // namespace os } // namespace os
#endif // __HAIKU__ #endif // __HAIKU__

View file

@ -0,0 +1,23 @@
#ifdef __HAIKU__
// clang-format off
#include <os/package/PackageDefs.h> // BPackageKit::BPackageInfoSet
#include <os/package/PackageInfoSet.h> // BPackageKit::BPackageInfo
#include <os/package/PackageRoster.h> // BPackageKit::BPackageRoster
// clang-format on
namespace package {
fn GetPackageCount() -> Result<u64, DracError> {
BPackageKit::BPackageRoster roster;
BPackageKit::BPackageInfoSet packageList;
const status_t status = roster.GetActivePackages(BPackageKit::B_PACKAGE_INSTALLATION_LOCATION_SYSTEM, packageList);
if (status != B_OK)
return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to get active package list"));
return static_cast<u64>(packageList.CountInfos());
}
} // namespace package
#endif // __HAIKU__

View file

@ -27,7 +27,6 @@
#include "src/wrappers/wayland.hpp" #include "src/wrappers/wayland.hpp"
#include "src/wrappers/xcb.hpp" #include "src/wrappers/xcb.hpp"
#include "linux/pkg_count.hpp"
#include "os.hpp" #include "os.hpp"
// clang-format on // clang-format on
@ -471,17 +470,6 @@ namespace os {
.total_bytes = stat.f_blocks * stat.f_frsize, .total_bytes = stat.f_blocks * stat.f_frsize,
}; };
} }
fn GetPackageCount() -> Result<u64, DracError> {
u64 count = 0;
if (Result<u64, DracError> linuxCount = linux::GetTotalPackageCount())
count += *linuxCount;
else
debug_at(linuxCount.error());
return count;
}
} // namespace os } // namespace os
#endif // __linux__ #endif // __linux__

View file

@ -1,248 +1,49 @@
#ifdef __linux__ #ifdef __linux__
// clang-format off // clang-format off
#include "src/os/linux/pkg_count.hpp" #include "src/core/package.hpp"
#include <SQLiteCpp/Database.h> // SQLite::{Database, OPEN_READONLY} #include <SQLiteCpp/Database.h> // SQLite::{Database, OPEN_READONLY}
#include <SQLiteCpp/Exception.h> // SQLite::Exception #include <SQLiteCpp/Exception.h> // SQLite::Exception
#include <SQLiteCpp/Statement.h> // SQLite::Statement #include <SQLiteCpp/Statement.h> // SQLite::Statement
#include <chrono> // std::chrono::{duration_cast, seconds, system_clock}
#include <filesystem> // std::filesystem::{current_path, directory_entry, directory_iterator, etc.} #include <filesystem> // std::filesystem::{current_path, directory_entry, directory_iterator, etc.}
#include <format> // std::format
#include <future> // std::{async, launch}
#include <glaze/beve/read.hpp> // glz::read_beve #include <glaze/beve/read.hpp> // glz::read_beve
#include <glaze/beve/write.hpp> // glz::write_beve #include <glaze/beve/write.hpp> // glz::write_beve
#include <system_error> // std::error_code
#include "src/util/cache.hpp"
#include "src/util/defs.hpp" #include "src/util/defs.hpp"
#include "src/util/error.hpp" #include "src/util/error.hpp"
#include "src/util/logging.hpp"
#include "src/util/types.hpp" #include "src/util/types.hpp"
// clang-format on // clang-format on
using util::error::DracError, util::error::DracErrorCode; using util::error::DracError;
using util::types::u64, util::types::i64, util::types::Result, util::types::Err, util::types::String, using util::types::u64, util::types::i64, util::types::Result, util::types::Err, util::types::String,
util::types::StringView, util::types::Exception; util::types::StringView, util::types::Exception;
using namespace std::string_literals;
namespace { namespace package {
namespace fs = std::filesystem; fn GetDpkgCount() -> Result<u64, DracError> {
using namespace std::chrono; return GetCountFromDirectory("Dpkg", fs::current_path().root_path() / "var" / "lib" / "dpkg" / "info", ".list"s);
using namespace util::cache;
using os::linux::PkgCountCacheData, os::linux::PackageManagerInfo;
fn GetPackageCountInternalDir(
const String& pmId,
const fs::path& dirPath,
const String& file_extension_filter = "",
const bool subtract_one = false
) -> Result<u64, DracError> {
debug_log("Attempting to get {} package count.", pmId);
std::error_code errc;
if (!fs::exists(dirPath, errc)) {
if (errc)
return Err(DracError(
DracErrorCode::IoError, std::format("Filesystem error checking {} directory: {}", pmId, errc.message())
));
return Err(
DracError(DracErrorCode::ApiUnavailable, std::format("{} directory not found: {}", pmId, dirPath.string()))
);
} }
if (!fs::is_directory(dirPath, errc)) { fn GetMossCount() -> Result<u64, DracError> {
if (errc)
return Err(DracError(
DracErrorCode::IoError, std::format("Filesystem error checking {} path type: {}", pmId, errc.message())
));
warn_log("Expected {} directory at '{}', but it's not a directory.", pmId, dirPath.string());
return Err(
DracError(DracErrorCode::IoError, std::format("{} path is not a directory: {}", pmId, dirPath.string()))
);
}
u64 count = 0;
try {
const fs::directory_iterator dirIter(dirPath, fs::directory_options::skip_permission_denied, errc);
if (errc)
return Err(
DracError(DracErrorCode::IoError, std::format("Failed to iterate {} directory: {}", pmId, errc.message()))
);
for (const fs::directory_entry& entry : dirIter) {
if (!file_extension_filter.empty()) {
if (std::error_code fileErrc; !entry.is_regular_file(fileErrc) || fileErrc) {
if (fileErrc)
warn_log(
"Error checking file status in {} directory for '{}': {}",
pmId,
entry.path().string(),
fileErrc.message()
);
continue;
}
if (entry.path().extension().string() == file_extension_filter)
count++;
} else {
count++;
}
}
} catch (const fs::filesystem_error& e) {
return Err(DracError(
DracErrorCode::IoError,
std::format("Filesystem error iterating {} directory '{}': {}", pmId, dirPath.string(), e.what())
));
} catch (...) {
return Err(DracError(
DracErrorCode::Other, std::format("Unknown error iterating {} directory '{}'", pmId, dirPath.string())
));
}
if (subtract_one && count > 0)
count--;
return count;
}
fn GetPackageCountInternalDb(const PackageManagerInfo& pmInfo) -> Result<u64, DracError> {
const auto& [pmId, dbPath, countQuery] = pmInfo;
if (Result<PkgCountCacheData, DracError> cachedDataResult = ReadCache<PkgCountCacheData>(pmId)) {
const auto& [count, timestamp] = *cachedDataResult;
std::error_code errc;
const std::filesystem::file_time_type dbModTime = fs::last_write_time(dbPath, errc);
if (errc) {
warn_log(
"Could not get modification time for '{}': {}. Invalidating {} cache.", dbPath.string(), errc.message(), pmId
);
} else {
if (const system_clock::time_point cacheTimePoint = system_clock::time_point(seconds(timestamp));
cacheTimePoint.time_since_epoch() >= dbModTime.time_since_epoch()) {
debug_log(
"Using valid {} package count cache (DB file unchanged since {}).",
pmId,
std::format("{:%F %T %Z}", floor<seconds>(cacheTimePoint))
);
return count;
}
debug_log("{} package count cache stale (DB file modified).", pmId);
}
} else {
if (cachedDataResult.error().code != DracErrorCode::NotFound)
debug_at(cachedDataResult.error());
debug_log("{} package count cache not found or unreadable.", pmId);
}
debug_log("Fetching fresh {} package count from database: {}", pmId, dbPath.string());
u64 count = 0;
try {
const SQLite::Database database(dbPath.string(), SQLite::OPEN_READONLY);
if (SQLite::Statement query(database, countQuery); query.executeStep()) {
const i64 countInt64 = query.getColumn(0).getInt64();
if (countInt64 < 0)
return Err(
DracError(DracErrorCode::ParseError, std::format("Negative count returned by {} DB COUNT query.", pmId))
);
count = static_cast<u64>(countInt64);
} else {
return Err(DracError(DracErrorCode::ParseError, std::format("No rows returned by {} DB COUNT query.", pmId)));
}
} catch (const SQLite::Exception& e) {
return Err(DracError(
DracErrorCode::ApiUnavailable, std::format("SQLite error occurred accessing {} DB: {}", pmId, e.what())
));
} catch (const Exception& e) { return Err(DracError(DracErrorCode::InternalError, e.what())); } catch (...) {
return Err(DracError(DracErrorCode::Other, std::format("Unknown error occurred accessing {} DB", pmId)));
}
const i64 nowEpochSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
const PkgCountCacheData dataToCache = { .count = count, .timestampEpochSeconds = nowEpochSeconds };
if (Result<void, DracError> writeResult = WriteCache(pmId, dataToCache); !writeResult)
error_at(writeResult.error());
return count;
}
} // namespace
namespace os::linux {
fn GetDpkgPackageCount() -> Result<u64, DracError> {
return GetPackageCountInternalDir(
"Dpkg", fs::current_path().root_path() / "var" / "lib" / "dpkg" / "info", String(".list")
);
}
fn GetMossPackageCount() -> Result<u64, DracError> {
debug_log("Attempting to get Moss package count.");
const PackageManagerInfo mossInfo = { const PackageManagerInfo mossInfo = {
.id = "moss", .id = "moss",
.dbPath = "/.moss/db/install", .dbPath = "/.moss/db/install",
.countQuery = "SELECT COUNT(*) FROM meta", .countQuery = "SELECT COUNT(*) FROM meta",
}; };
if (std::error_code errc; !fs::exists(mossInfo.dbPath, errc)) { Result<u64, DracError> countResult = GetCountFromDb(mossInfo);
if (errc) {
warn_log("Filesystem error checking for Moss DB at '{}': {}", mossInfo.dbPath.string(), errc.message());
return Err(DracError(DracErrorCode::IoError, "Filesystem error checking Moss DB: " + errc.message()));
}
return Err(DracError(DracErrorCode::ApiUnavailable, "Moss db not found: " + mossInfo.dbPath.string()));
}
Result<u64, DracError> countResult = GetPackageCountInternalDb(mossInfo);
if (!countResult) {
if (countResult.error().code != DracErrorCode::ParseError)
debug_at(countResult.error());
return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to get package count from Moss DB"));
}
if (countResult)
if (*countResult > 0)
return *countResult - 1; return *countResult - 1;
return countResult;
} }
fn GetPacmanPackageCount() -> Result<u64, DracError> { fn GetPacmanCount() -> Result<u64, DracError> {
return GetPackageCountInternalDir( return GetCountFromDirectory("Pacman", fs::current_path().root_path() / "var" / "lib" / "pacman" / "local", true);
"Pacman", fs::current_path().root_path() / "var" / "lib" / "pacman" / "local", "", true
);
} }
} // namespace package
fn GetTotalPackageCount() -> Result<u64, DracError> {
using util::types::Array, util::types::Future;
Array<Future<Result<u64, DracError>>, 3> futures = {
std::async(std::launch::async, GetDpkgPackageCount),
std::async(std::launch::async, GetMossPackageCount),
std::async(std::launch::async, GetPacmanPackageCount),
};
u64 totalCount = 0;
for (Future<Result<u64, DracError>>& fut : futures) try {
if (Result<u64, DracError> result = fut.get()) {
totalCount += *result;
} else {
if (result.error().code != DracErrorCode::ApiUnavailable) {
error_at(result.error());
} else {
debug_at(result.error());
}
}
} catch (const Exception& e) {
error_log("Caught exception while getting package count future: {}", e.what());
} catch (...) { error_log("Caught unknown exception while getting package count future."); }
return totalCount;
}
} // namespace os::linux
#endif // __linux__ #endif // __linux__

View file

@ -1,77 +0,0 @@
#pragma once
#ifdef __linux__
// clang-format off
#include <filesystem> // std::filesystem::path
#include <glaze/core/common.hpp> // glz::object
#include <glaze/core/meta.hpp> // glz::detail::Object
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/types.hpp"
// clang-format on
namespace os::linux {
using util::error::DracError;
using util::types::Result, util::types::u64, util::types::i64, util::types::String;
namespace fs = std::filesystem;
struct PackageManagerInfo {
String id;
fs::path dbPath;
String countQuery;
};
struct PkgCountCacheData {
u64 count {};
i64 timestampEpochSeconds {};
// NOLINTBEGIN(readability-identifier-naming)
struct [[maybe_unused]] glaze {
using T = PkgCountCacheData;
static constexpr glz::detail::Object value =
glz::object("count", &T::count, "timestamp", &T::timestampEpochSeconds);
};
// NOLINTEND(readability-identifier-naming)
};
// Get package count from dpkg (Debian/Ubuntu)
fn GetDpkgPackageCount() -> Result<u64, DracError>;
// Get package count from RPM (Red Hat/Fedora/CentOS)
fn GetRpmPackageCount() -> Result<u64, DracError>;
// Get package count from pacman (Arch Linux)
fn GetPacmanPackageCount() -> Result<u64, DracError>;
// Get package count from Portage (Gentoo)
fn GetPortagePackageCount() -> Result<u64, DracError>;
// Get package count from zypper (openSUSE)
fn GetZypperPackageCount() -> Result<u64, DracError>;
// Get package count from apk (Alpine)
fn GetApkPackageCount() -> Result<u64, DracError>;
// Get package count from moss (AerynOS)
fn GetMossPackageCount() -> Result<u64, DracError>;
// Get package count from nix
fn GetNixPackageCount() -> Result<u64, DracError>;
// Get package count from flatpak
fn GetFlatpakPackageCount() -> Result<u64, DracError>;
// Get package count from snap
fn GetSnapPackageCount() -> Result<u64, DracError>;
// Get package count from AppImage
fn GetAppimagePackageCount() -> Result<u64, DracError>;
// Get total package count from all available package managers
fn GetTotalPackageCount() -> Result<u64, DracError>;
} // namespace os::linux
#endif // __linux__

View file

@ -84,15 +84,6 @@ namespace os {
*/ */
fn GetKernelVersion() -> Result<String, DracError>; fn GetKernelVersion() -> Result<String, DracError>;
/**
* @brief Gets the number of installed packages (Platform-specific).
* @details On Linux, sums counts from various package managers. On other platforms, behavior may vary.
* @return A Result containing the package count (u64) on success,
* or a DracError on failure (e.g., permission errors, command not found)
* or if not supported (DracErrorCode::NotSupported).
*/
fn GetPackageCount() -> Result<u64, DracError>;
/** /**
* @brief Gets the disk usage for the primary/root filesystem. * @brief Gets the disk usage for the primary/root filesystem.
* @details Uses statvfs on Linux/macOS, GetDiskFreeSpaceExW on Windows. * @details Uses statvfs on Linux/macOS, GetDiskFreeSpaceExW on Windows.

View file

@ -6,7 +6,6 @@
#include <glaze/beve/write.hpp> // glz::write_beve #include <glaze/beve/write.hpp> // glz::write_beve
#include <glaze/core/context.hpp> // glz::{context, error_code, error_ctx} #include <glaze/core/context.hpp> // glz::{context, error_code, error_ctx}
#include <iterator> // std::istreambuf_iterator #include <iterator> // std::istreambuf_iterator
#include <string> // std::string
#include <system_error> // std::error_code #include <system_error> // std::error_code
#include <type_traits> // std::decay_t #include <type_traits> // std::decay_t