unfinished but im tired
This commit is contained in:
parent
71f9c1ce63
commit
d693c8cfb1
13 changed files with 457 additions and 531 deletions
|
@ -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
335
src/core/package.cpp
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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__
|
||||||
|
|
|
@ -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
|
|
|
@ -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__
|
||||||
|
|
23
src/os/haiku/pkg_count.cpp
Normal file
23
src/os/haiku/pkg_count.cpp
Normal 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__
|
|
@ -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__
|
||||||
|
|
|
@ -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__
|
||||||
|
|
|
@ -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__
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue