From 44c2c99468476a41cd09c21b158253c5073c3421 Mon Sep 17 00:00:00 2001 From: Mars Date: Thu, 1 May 2025 12:40:34 -0400 Subject: [PATCH] fix macos --- flake.nix | 4 +- meson.build | 2 +- src/os/linux/pkg_count.cpp | 209 ---------------------------- src/os/macos.cpp | 39 +++--- src/os/macos/bridge.hpp | 8 +- src/os/macos/bridge.mm | 16 +-- src/os/shared.cpp | 273 ++++++++++++++++++++++++++++++++++++- 7 files changed, 303 insertions(+), 248 deletions(-) diff --git a/flake.nix b/flake.nix index 09d0df5..aa30691 100644 --- a/flake.nix +++ b/flake.nix @@ -163,7 +163,7 @@ then stdenvAdapters.useMoldLinker else lib.id ) - llvmPackages.stdenv; + llvmPackages.libcxxStdenv; deps = with pkgs; [ @@ -172,6 +172,7 @@ ++ (with pkgsStatic; [ curl ftxui + sqlitecpp (tomlplusplus.overrideAttrs { doCheck = false; }) @@ -187,7 +188,6 @@ ] ++ (with pkgsStatic; [ dbus - sqlitecpp xorg.libxcb wayland ])); diff --git a/meson.build b/meson.build index efda771..a0da6b4 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,3 @@ -# ----------------------- # # Project Configuration # # ----------------------- # project( @@ -110,6 +109,7 @@ platform_deps = [] if host_system == 'darwin' platform_deps += [ + dependency('SQLiteCpp'), dependency( 'appleframeworks', modules : ['foundation', 'mediaplayer', 'systemconfiguration'], diff --git a/src/os/linux/pkg_count.cpp b/src/os/linux/pkg_count.cpp index b4df757..ab9b79e 100644 --- a/src/os/linux/pkg_count.cpp +++ b/src/os/linux/pkg_count.cpp @@ -33,194 +33,6 @@ namespace { using namespace std::chrono; using os::linux::PkgCountCacheData, os::linux::PackageManagerInfo; - constexpr StringView ALLOWED_PMID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"; - - fn GetPkgCountCachePath(const String& pmId) -> Result { - std::error_code errc; - const fs::path cacheDir = fs::temp_directory_path(errc); - - if (errc) - return Err(DracError(DracErrorCode::IoError, "Failed to get temp directory: " + errc.message())); - - if (pmId.empty() || pmId.find_first_not_of(ALLOWED_PMID_CHARS) != String::npos) - return Err(DracError(DracErrorCode::ParseError, "Invalid package manager ID for cache path: " + pmId)); - - return cacheDir / (pmId + "_pkg_count_cache.beve"); - } - - fn ReadPkgCountCache(const String& pmId) -> Result { - Result cachePathResult = GetPkgCountCachePath(pmId); - - if (!cachePathResult) - return Err(cachePathResult.error()); - - const fs::path& cachePath = *cachePathResult; - - if (!fs::exists(cachePath)) - return Err(DracError(DracErrorCode::NotFound, "Cache file not found: " + cachePath.string())); - - std::ifstream ifs(cachePath, std::ios::binary); - if (!ifs.is_open()) - return Err(DracError(DracErrorCode::IoError, "Failed to open cache file for reading: " + cachePath.string())); - - try { - const String content((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); - ifs.close(); - - if (content.empty()) - return Err(DracError(DracErrorCode::ParseError, "BEVE cache file is empty: " + cachePath.string())); - - PkgCountCacheData result; - const glz::context ctx {}; - - if (glz::error_ctx glazeResult = glz::read_beve(result, content); glazeResult.ec != glz::error_code::none) - return Err(DracError( - DracErrorCode::ParseError, - std::format( - "BEVE parse error reading cache (code {}): {}", static_cast(glazeResult.ec), cachePath.string() - ) - )); - - return result; - } catch (const std::ios_base::failure& e) { - return Err(DracError( - DracErrorCode::IoError, std::format("Filesystem error reading cache file {}: {}", cachePath.string(), e.what()) - )); - } catch (const Exception& e) { - return Err(DracError(DracErrorCode::InternalError, std::format("Error reading package count cache: {}", e.what())) - ); - } - } - - fn WritePkgCountCache(const String& pmId, const PkgCountCacheData& data) -> Result { - using util::types::isize; - - Result cachePathResult = GetPkgCountCachePath(pmId); - - if (!cachePathResult) - return Err(cachePathResult.error()); - - const fs::path& cachePath = *cachePathResult; - fs::path tempPath = cachePath; - tempPath += ".tmp"; - - try { - String binaryBuffer; - - PkgCountCacheData mutableData = data; - - if (glz::error_ctx glazeErr = glz::write_beve(mutableData, binaryBuffer); glazeErr) - return Err(DracError( - DracErrorCode::ParseError, - std::format("BEVE serialization error writing cache (code {})", static_cast(glazeErr.ec)) - )); - - { - std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc); - if (!ofs.is_open()) - return Err(DracError(DracErrorCode::IoError, "Failed to open temp cache file: " + tempPath.string())); - - ofs.write(binaryBuffer.data(), static_cast(binaryBuffer.size())); - - if (!ofs) { - std::error_code removeEc; - fs::remove(tempPath, removeEc); - return Err(DracError(DracErrorCode::IoError, "Failed to write to temp cache file: " + tempPath.string())); - } - } - - std::error_code errc; - fs::rename(tempPath, cachePath, errc); - if (errc) { - fs::remove(tempPath, errc); - return Err(DracError( - DracErrorCode::IoError, - std::format("Failed to replace cache file '{}': {}", cachePath.string(), errc.message()) - )); - } - - return {}; - } catch (const std::ios_base::failure& e) { - std::error_code removeEc; - fs::remove(tempPath, removeEc); - return Err(DracError( - DracErrorCode::IoError, std::format("Filesystem error writing cache file {}: {}", tempPath.string(), e.what()) - )); - } catch (const Exception& e) { - std::error_code removeEc; - fs::remove(tempPath, removeEc); - return Err(DracError(DracErrorCode::InternalError, std::format("Error writing package count cache: {}", e.what())) - ); - } catch (...) { - std::error_code removeEc; - fs::remove(tempPath, removeEc); - return Err(DracError(DracErrorCode::Other, std::format("Unknown error writing cache file: {}", tempPath.string())) - ); - } - } - - fn GetPackageCountInternalDb(const PackageManagerInfo& pmInfo) -> Result { - const auto& [pmId, dbPath, countQuery] = pmInfo; - - if (Result cachedDataResult = ReadPkgCountCache(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(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(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(system_clock::now().time_since_epoch()).count(); - const PkgCountCacheData dataToCache = { .count = count, .timestampEpochSeconds = nowEpochSeconds }; - - if (Result writeResult = WritePkgCountCache(pmId, dataToCache); !writeResult) - error_at(writeResult.error()); - - return count; - } - fn GetPackageCountInternalDir( const String& pmId, const fs::path& dirPath, @@ -338,27 +150,6 @@ namespace os::linux { return *countResult - 1; } - fn GetNixPackageCount() -> Result { - debug_log("Attempting to get Nix package count."); - - 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 GetPackageCountInternalDb(nixInfo); - } - fn GetPacmanPackageCount() -> Result { return GetPackageCountInternalDir( "Pacman", fs::current_path().root_path() / "var" / "lib" / "pacman" / "local", "", true diff --git a/src/os/macos.cpp b/src/os/macos.cpp index 8079674..aed8727 100644 --- a/src/os/macos.cpp +++ b/src/os/macos.cpp @@ -13,41 +13,46 @@ // clang-format on using namespace util::types; +using util::error::DracError, util::error::DracErrorCode; -fn os::GetMemInfo() -> Result { +fn os::GetMemInfo() -> Result { u64 mem = 0; usize size = sizeof(mem); if (sysctlbyname("hw.memsize", &mem, &size, nullptr, 0) == -1) - return Err(DraconisError::withErrno("Failed to get memory info")); + return Err(DracError::withErrno("Failed to get memory info")); return mem; } -fn os::GetNowPlaying() -> Result { return GetCurrentPlayingInfo(); } +fn os::GetNowPlaying() -> Result { return GetCurrentPlayingInfo(); } -fn os::GetOSVersion() -> Result { return GetMacOSVersion(); } +fn os::GetOSVersion() -> Result { return GetMacOSVersion(); } -fn os::GetDesktopEnvironment() -> Option { return None; } +fn os::GetDesktopEnvironment() -> Result { + return Err(DracError(DracErrorCode::Other, "Not implemented on macOS")); +} -fn os::GetWindowManager() -> Option { return None; } +fn os::GetWindowManager() -> Result { + return Err(DracError(DracErrorCode::Other, "Not implemented on macOS")); +} -fn os::GetKernelVersion() -> Result { +fn os::GetKernelVersion() -> Result { std::array kernelVersion {}; usize kernelVersionLen = sizeof(kernelVersion); if (sysctlbyname("kern.osrelease", kernelVersion.data(), &kernelVersionLen, nullptr, 0) == -1) - return Err(DraconisError::withErrno("Failed to get kernel version")); + return Err(DracError::withErrno("Failed to get kernel version")); return kernelVersion.data(); } -fn os::GetHost() -> Result { +fn os::GetHost() -> Result { std::array hwModel {}; size_t hwModelLen = sizeof(hwModel); if (sysctlbyname("hw.model", hwModel.data(), &hwModelLen, nullptr, 0) == -1) - return Err(DraconisError::withErrno("Failed to get host info")); + return Err(DracError::withErrno("Failed to get host info")); // taken from https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/host/host_mac.c // shortened a lot of the entries to remove unnecessary info @@ -202,27 +207,25 @@ fn os::GetHost() -> Result { const auto iter = modelNameByHwModel.find(hwModel.data()); if (iter == modelNameByHwModel.end()) - return Err(DraconisError::withErrno("Failed to get host info")); + return Err(DracError::withErrno("Failed to get host info")); return String(iter->second); } -fn os::GetDiskUsage() -> Result { +fn os::GetDiskUsage() -> Result { struct statvfs vfs; if (statvfs("/", &vfs) != 0) - return Err(DraconisError::withErrno("Failed to get disk usage")); + return Err(DracError::withErrno("Failed to get disk usage")); return DiskSpace { .used_bytes = (vfs.f_blocks - vfs.f_bfree) * vfs.f_frsize, .total_bytes = vfs.f_blocks * vfs.f_frsize }; } -fn os::GetPackageCount() -> Result { - using util::error::DraconisErrorCode; +fn os::GetPackageCount() -> Result { return shared::GetPackageCount(); } - return Err(DraconisError(DraconisErrorCode::Other, "Not implemented on macOS")); +fn os::GetShell() -> Result { + return Err(DracError(DracErrorCode::Other, "Not implemented on macOS")); } -fn os::GetShell() -> Option { return None; } - #endif diff --git a/src/os/macos/bridge.hpp b/src/os/macos/bridge.hpp index 713bb7f..13d2248 100644 --- a/src/os/macos/bridge.hpp +++ b/src/os/macos/bridge.hpp @@ -7,7 +7,7 @@ #include "src/core/util/error.hpp" #include "src/core/util/types.hpp" // clang-format on -using util::error::DraconisError; +using util::error::DracError; using util::types::MediaInfo, util::types::String, util::types::Result; #ifdef __OBJC__ @@ -15,12 +15,12 @@ using util::types::MediaInfo, util::types::String, util::types::Result; @interface Bridge : NSObject + (void)fetchCurrentPlayingMetadata:(void (^)(Result))completion; -+ (Result)macOSVersion; ++ (Result)macOSVersion; @end #else extern "C++" { - fn GetCurrentPlayingInfo() -> Result; - fn GetMacOSVersion() -> Result; + fn GetCurrentPlayingInfo() -> Result; + fn GetMacOSVersion() -> Result; } #endif #endif diff --git a/src/os/macos/bridge.mm b/src/os/macos/bridge.mm index 366c0e3..4bdd8eb 100644 --- a/src/os/macos/bridge.mm +++ b/src/os/macos/bridge.mm @@ -14,8 +14,8 @@ #include "src/core/util/error.hpp" // clang-format on -using util::error::DraconisErrorCode; -using util::types::Err, util::types::Option; +using util::error::DracErrorCode; +using util::types::Err, util::types::Option, util::types::None; using MRMediaRemoteGetNowPlayingInfoFunction = void (*)(dispatch_queue_t queue, void (^handler)(NSDictionary* information)); @@ -66,7 +66,7 @@ using MRMediaRemoteGetNowPlayingInfoFunction = ); } -+ (Result)macOSVersion { ++ (Result)macOSVersion { NSProcessInfo* processInfo = [NSProcessInfo processInfo]; NSOperatingSystemVersion osVersion = [processInfo operatingSystemVersion]; @@ -91,21 +91,21 @@ using MRMediaRemoteGetNowPlayingInfoFunction = extern "C++" { // NOLINTBEGIN(misc-use-internal-linkage) - fn GetCurrentPlayingInfo() -> Result { - __block Result result; + fn GetCurrentPlayingInfo() -> Result { + __block Result result; const dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [Bridge fetchCurrentPlayingMetadata:^(std::expected metadataResult) { if (!metadataResult) { - result = Err(DraconisError(DraconisErrorCode::InternalError, metadataResult.error())); + result = Err(DracError(DracErrorCode::InternalError, metadataResult.error())); dispatch_semaphore_signal(semaphore); return; } const NSDictionary* const metadata = *metadataResult; if (!metadata) { - result = Err(DraconisError(DraconisErrorCode::InternalError, "No metadata")); + result = Err(DracError(DracErrorCode::InternalError, "No metadata")); dispatch_semaphore_signal(semaphore); return; } @@ -124,7 +124,7 @@ extern "C++" { return result; } - fn GetMacOSVersion() -> Result { return [Bridge macOSVersion]; } + fn GetMacOSVersion() -> Result { return [Bridge macOSVersion]; } // NOLINTEND(misc-use-internal-linkage) } diff --git a/src/os/shared.cpp b/src/os/shared.cpp index 52b2c4e..ec8c394 100644 --- a/src/os/shared.cpp +++ b/src/os/shared.cpp @@ -1,4 +1,16 @@ -#include +#include // SQLite::{Database, OPEN_READONLY} +#include // SQLite::Exception +#include // SQLite::Statement +#include // std::chrono +#include // std::filesystem +#include // std::{ifstream, ofstream} +#include // glz::read_beve +#include // glz::write_beve +#include // glz::object +#include // glz::{context, error_code, error_ctx} +#include // glz::detail::Object +#include // std::ios::{binary, trunc}, std::ios_base +#include // std::istreambuf_iterator #include "src/core/util/defs.hpp" #include "src/core/util/error.hpp" @@ -8,13 +20,245 @@ #include "os.hpp" -namespace os::shared { - using util::error::DracError, util::error::DracErrorCode; - using util::types::u64, util::types::String, util::types::Result, util::types::Err; +using util::error::DracError, util::error::DracErrorCode; +using util::types::u64, util::types::i64, util::types::String, util::types::StringView, util::types::Result, + util::types::Err, util::types::Exception; - namespace fs = std::filesystem; +namespace fs = std::filesystem; - fn GetPackageCount() -> Result { +namespace { + using namespace std::chrono; + + 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) + }; + + constexpr StringView ALLOWED_PMID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"; + + fn GetPkgCountCachePath(const String& pmId) -> Result { + std::error_code errc; + const fs::path cacheDir = fs::temp_directory_path(errc); + + if (errc) + return Err(DracError(DracErrorCode::IoError, "Failed to get temp directory: " + errc.message())); + + if (pmId.empty() || pmId.find_first_not_of(ALLOWED_PMID_CHARS) != String::npos) + return Err(DracError(DracErrorCode::ParseError, "Invalid package manager ID for cache path: " + pmId)); + + return cacheDir / (pmId + "_pkg_count_cache.beve"); + } + + fn ReadPkgCountCache(const String& pmId) -> Result { + Result cachePathResult = GetPkgCountCachePath(pmId); + + if (!cachePathResult) + return Err(cachePathResult.error()); + + const fs::path& cachePath = *cachePathResult; + + if (!fs::exists(cachePath)) + return Err(DracError(DracErrorCode::NotFound, "Cache file not found: " + cachePath.string())); + + std::ifstream ifs(cachePath, std::ios::binary); + if (!ifs.is_open()) + return Err(DracError(DracErrorCode::IoError, "Failed to open cache file for reading: " + cachePath.string())); + + try { + const String content((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + ifs.close(); + + if (content.empty()) + return Err(DracError(DracErrorCode::ParseError, "BEVE cache file is empty: " + cachePath.string())); + + PkgCountCacheData result; + const glz::context ctx {}; + + if (glz::error_ctx glazeResult = glz::read_beve(result, content); glazeResult.ec != glz::error_code::none) + return Err(DracError( + DracErrorCode::ParseError, + std::format( + "BEVE parse error reading cache (code {}): {}", static_cast(glazeResult.ec), cachePath.string() + ) + )); + + return result; + } catch (const std::ios_base::failure& e) { + return Err(DracError( + DracErrorCode::IoError, std::format("Filesystem error reading cache file {}: {}", cachePath.string(), e.what()) + )); + } catch (const Exception& e) { + return Err(DracError(DracErrorCode::InternalError, std::format("Error reading package count cache: {}", e.what())) + ); + } + } + + fn WritePkgCountCache(const String& pmId, const PkgCountCacheData& data) -> Result { + using util::types::isize; + + Result cachePathResult = GetPkgCountCachePath(pmId); + + if (!cachePathResult) + return Err(cachePathResult.error()); + + const fs::path& cachePath = *cachePathResult; + fs::path tempPath = cachePath; + tempPath += ".tmp"; + + try { + String binaryBuffer; + + PkgCountCacheData mutableData = data; + + if (glz::error_ctx glazeErr = glz::write_beve(mutableData, binaryBuffer); glazeErr) + return Err(DracError( + DracErrorCode::ParseError, + std::format("BEVE serialization error writing cache (code {})", static_cast(glazeErr.ec)) + )); + + { + std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc); + if (!ofs.is_open()) + return Err(DracError(DracErrorCode::IoError, "Failed to open temp cache file: " + tempPath.string())); + + ofs.write(binaryBuffer.data(), static_cast(binaryBuffer.size())); + + if (!ofs) { + std::error_code removeEc; + fs::remove(tempPath, removeEc); + return Err(DracError(DracErrorCode::IoError, "Failed to write to temp cache file: " + tempPath.string())); + } + } + + std::error_code errc; + fs::rename(tempPath, cachePath, errc); + if (errc) { + fs::remove(tempPath, errc); + return Err(DracError( + DracErrorCode::IoError, + std::format("Failed to replace cache file '{}': {}", cachePath.string(), errc.message()) + )); + } + + return {}; + } catch (const std::ios_base::failure& e) { + std::error_code removeEc; + fs::remove(tempPath, removeEc); + return Err(DracError( + DracErrorCode::IoError, std::format("Filesystem error writing cache file {}: {}", tempPath.string(), e.what()) + )); + } catch (const Exception& e) { + std::error_code removeEc; + fs::remove(tempPath, removeEc); + return Err(DracError(DracErrorCode::InternalError, std::format("Error writing package count cache: {}", e.what())) + ); + } catch (...) { + std::error_code removeEc; + fs::remove(tempPath, removeEc); + return Err(DracError(DracErrorCode::Other, std::format("Unknown error writing cache file: {}", tempPath.string())) + ); + } + } + + fn GetPackageCountInternalDb(const PackageManagerInfo& pmInfo) -> Result { + const auto& [pmId, dbPath, countQuery] = pmInfo; + + if (Result cachedDataResult = ReadPkgCountCache(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(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(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(system_clock::now().time_since_epoch()).count(); + const PkgCountCacheData dataToCache = { .count = count, .timestampEpochSeconds = nowEpochSeconds }; + + if (Result writeResult = WritePkgCountCache(pmId, dataToCache); !writeResult) + error_at(writeResult.error()); + + return count; + } + + fn GetNixPackageCount() -> Result { + debug_log("Attempting to get Nix package count."); + + 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 GetPackageCountInternalDb(nixInfo); + } + + fn GetCargoPackageCount() -> Result { using util::helpers::GetEnv; fs::path cargoPath {}; @@ -35,6 +279,23 @@ namespace os::shared { debug_log("Found {} packages in cargo directory: {}", count, cargoPath.string()); + return count; + } +} // namespace + +namespace os::shared { + fn GetPackageCount() -> Result { + u64 count = 0; + if (Result pkgCount = GetNixPackageCount()) + count += *pkgCount; + else + debug_at(pkgCount.error()); + + if (Result pkgCount = GetCargoPackageCount()) + count += *pkgCount; + else + debug_at(pkgCount.error()); + return count; } } // namespace os::shared