diff --git a/flake.lock b/flake.lock index 5f0eea5..d40170b 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1738012240, - "narHash": "sha256-4wmhkSSdgkVR02zG7nP4MTUpA2oih7E+9AWu4zEqP+k=", + "lastModified": 1738611518, + "narHash": "sha256-gOP/qsGtUCTkazx3qQ/tn6xaDERRgOtF2eRe1gmIU5s=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "0542e87760a8f611f089dcf38862f783c8c8f890", + "rev": "eb3431789cef743af9dace58eb2ba7b33a332b56", "type": "github" }, "original": { @@ -58,11 +58,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1737483750, - "narHash": "sha256-5An1wq5U8sNycOBBg3nsDDgpwBmR9liOpDGlhliA6Xo=", + "lastModified": 1738070913, + "narHash": "sha256-j6jC12vCFsTGDmY2u1H12lMr62fnclNjuCtAdF1a4Nk=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "f2cc121df15418d028a59c9737d38e3a90fbaf8f", + "rev": "bebf27d00f7d10ba75332a0541ac43676985dea3", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index bdf5cd1..12c3e7b 100644 --- a/flake.nix +++ b/flake.nix @@ -65,6 +65,7 @@ tomlplusplus yyjson reflect-cpp + sqlitecpp ftxui ] ++ linuxPkgs @@ -75,7 +76,6 @@ systemdLibs sdbus-cpp valgrind - linuxKernel.packages.linux_zen.perf.out xorg.libX11 wayland ]); diff --git a/meson.build b/meson.build index ca66fc9..161bf49 100644 --- a/meson.build +++ b/meson.build @@ -92,6 +92,7 @@ if host_machine.system() == 'darwin' deps += dependency('SystemConfiguration') deps += dependency('iconv') elif host_machine.system() == 'linux' or host_machine.system() == 'freebsd' + deps += dependency('SQLiteCpp') deps += dependency('sdbus-c++') deps += dependency('x11') deps += dependency('wayland-client') @@ -113,4 +114,4 @@ executable( objc_args: objc_args, link_args: link_args, dependencies: deps, -) \ No newline at end of file +) diff --git a/src/config/weather.cpp b/src/config/weather.cpp index 36fd6dc..bf0587a 100644 --- a/src/config/weather.cpp +++ b/src/config/weather.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -7,9 +8,12 @@ #include "config.h" -using rfl::Error; -using rfl::Result; namespace fs = std::filesystem; +using namespace std::string_literals; + +// Alias for cleaner error handling +template +using Result = std::expected; namespace { // Common function to get cache path @@ -18,7 +22,7 @@ namespace { fs::path cachePath = fs::temp_directory_path(errc); if (errc) - return Error("Failed to get temp directory: " + errc.message()); + return std::unexpected("Failed to get temp directory: "s + errc.message()); cachePath /= "weather_cache.json"; return cachePath; @@ -28,91 +32,94 @@ namespace { fn ReadCacheFromFile() -> Result { Result cachePath = GetCachePath(); if (!cachePath) - return Error(cachePath.error()->what()); + return std::unexpected(cachePath.error()); std::ifstream ifs(*cachePath, std::ios::binary); if (!ifs.is_open()) - return Error("Cache file not found: " + cachePath.value().string()); + return std::unexpected("Cache file not found: "s + cachePath->string()); DEBUG_LOG("Reading from cache file..."); std::string content((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); - Result result = rfl::json::read(content); + rfl::Result result = rfl::json::read(content); + if (!result) + return std::unexpected(result.error()->what()); DEBUG_LOG("Successfully read from cache file."); - return result; + return *result; } // Function to write cache to file - fn WriteCacheToFile(const WeatherOutput& data) -> Result { + fn WriteCacheToFile(const WeatherOutput& data) -> Result { Result cachePath = GetCachePath(); if (!cachePath) - return Error(cachePath.error()->what()); + return std::unexpected(cachePath.error()); DEBUG_LOG("Writing to cache file..."); - // Write to temporary file first fs::path tempPath = *cachePath; tempPath += ".tmp"; { std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc); if (!ofs.is_open()) - return Error("Failed to open temp file: " + tempPath.string()); + return std::unexpected("Failed to open temp file: "s + tempPath.string()); - auto json = rfl::json::write(data); + std::string json = rfl::json::write(data); ofs << json; if (!ofs) - return Error("Failed to write to temp file"); - } // File stream closes here + return std::unexpected("Failed to write to temp file"); + } - // Atomic replace std::error_code errc; fs::rename(tempPath, *cachePath, errc); if (errc) { fs::remove(tempPath, errc); - return Error("Failed to replace cache file: " + errc.message()); + return std::unexpected("Failed to replace cache file: "s + errc.message()); } DEBUG_LOG("Successfully wrote to cache file."); - return 0; + return {}; } - fn WriteCallback(void* contents, const usize size, const usize nmemb, string* str) -> usize { - const usize totalSize = size * nmemb; + fn WriteCallback(void* contents, size_t size, size_t nmemb, std::string* str) -> size_t { + const size_t totalSize = size * nmemb; str->append(static_cast(contents), totalSize); return totalSize; } // Function to make API request - fn MakeApiRequest(const string& url) -> Result { + fn MakeApiRequest(const std::string& url) -> Result { DEBUG_LOG("Making API request to URL: {}", url); - CURL* curl = curl_easy_init(); - string responseBuffer; + CURL* curl = curl_easy_init(); + std::string responseBuffer; - if (curl) { - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBuffer); - const CURLcode res = curl_easy_perform(curl); - curl_easy_cleanup(curl); + if (!curl) + return std::unexpected("Failed to initialize cURL"); - if (res != CURLE_OK) - return Error(fmt::format("Failed to perform cURL request: {}", curl_easy_strerror(res))); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBuffer); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5); - DEBUG_LOG("Received response from API. Response size: {}", responseBuffer.size()); - DEBUG_LOG("Response: {}", responseBuffer); + const CURLcode res = curl_easy_perform(curl); + curl_easy_cleanup(curl); - WeatherOutput output = rfl::json::read(responseBuffer).value(); + if (res != CURLE_OK) + return std::unexpected(fmt::format("cURL error: {}", curl_easy_strerror(res))); - return output; // Return an empty result for now - } + DEBUG_LOG("API response size: {}", responseBuffer.size()); - return Error("Failed to initialize cURL."); + rfl::Result output = rfl::json::read(responseBuffer); + if (!output) + return std::unexpected(output.error()->what()); + + return *output; } } @@ -120,60 +127,48 @@ namespace { fn Weather::getWeatherInfo() const -> WeatherOutput { using namespace std::chrono; - // Check if cache is valid if (Result data = ReadCacheFromFile()) { - WeatherOutput dataVal = *data; - - if (system_clock::now() - system_clock::time_point(seconds(dataVal.dt)) < minutes(10)) { - DEBUG_LOG("Cache is valid. Returning cached data."); + const WeatherOutput& dataVal = *data; + const duration cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt)); + if (cacheAge < 10min) { + DEBUG_LOG("Using valid cache"); return dataVal; } - - DEBUG_LOG("Cache is expired."); + DEBUG_LOG("Cache expired"); } else { - DEBUG_LOG("No valid cache found."); + DEBUG_LOG("Cache error: {}", data.error()); } - WeatherOutput result; + fn handleApiResult = [](const Result& result) -> WeatherOutput { + if (!result) + ERROR_LOG("API request failed: {}", result.error()); - if (holds_alternative(location)) { - const string city = get(location); + // Fix for second warning: Check the write result + if (Result writeResult = WriteCacheToFile(*result); !writeResult) + ERROR_LOG("Failed to write cache: {}", writeResult.error()); - const char* loc = curl_easy_escape(nullptr, city.c_str(), static_cast(city.length())); + return *result; + }; - DEBUG_LOG("City: {}", loc); + if (std::holds_alternative(location)) { + const auto& city = std::get(location); + char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast(city.length())); - const string apiUrl = fmt::format( - "https://api.openweathermap.org/data/2.5/" - "weather?q={}&appid={}&units={}", - loc, - api_key, - units - ); + DEBUG_LOG("Requesting city: {}", escaped); + const std::string apiUrl = + fmt::format("https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units={}", escaped, api_key, units); - result = MakeApiRequest(apiUrl).value(); - } else { - const auto [lat, lon] = get(location); - - DEBUG_LOG("Coordinates: lat = {:.3f}, lon = {:.3f}", lat, lon); - - const string apiUrl = fmt::format( - "https://api.openweathermap.org/data/2.5/" - "weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", - lat, - lon, - api_key, - units - ); - - result = MakeApiRequest(apiUrl).value(); + curl_free(escaped); + return handleApiResult(MakeApiRequest(apiUrl)); } - // Update the cache with the new data - WriteCacheToFile(result); + const auto& [lat, lon] = std::get(location); + DEBUG_LOG("Requesting coordinates: lat={:.3f}, lon={:.3f}", lat, lon); - DEBUG_LOG("Returning new data."); + const std::string apiUrl = fmt::format( + "https://api.openweathermap.org/data/2.5/weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", lat, lon, api_key, units + ); - return result; + return handleApiResult(MakeApiRequest(apiUrl)); } diff --git a/src/main.cpp b/src/main.cpp index 03ec616..fce1eee 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,8 @@ #include "config/config.h" #include "os/os.h" +constexpr const bool SHOW_ICONS = true; + struct BytesToGiB { u64 value; }; @@ -83,18 +85,23 @@ namespace { const bool nowPlayingEnabled = config.now_playing.get().enabled; const std::string& nowPlaying = nowPlayingEnabled ? GetNowPlaying() : ""; - // Icon constants (using Nerd Font v3) - constexpr const char* calendarIcon = "  "; - constexpr const char* hostIcon = " 󰌢 "; - constexpr const char* kernelIcon = "  "; - constexpr const char* osIcon = "  "; - constexpr const char* memoryIcon = "  "; - constexpr const char* weatherIcon = "  "; - constexpr const char* musicIcon = "  "; - const Color::Palette16 labelColor = Color::Yellow; - const Color::Palette16 valueColor = Color::White; - const Color::Palette16 borderColor = Color::GrayLight; - const Color::Palette16 iconColor = Color::Cyan; + const char *calendarIcon = "", *hostIcon = "", *kernelIcon = "", *osIcon = "", *memoryIcon = "", *weatherIcon = "", + *musicIcon = ""; + + if (SHOW_ICONS) { + calendarIcon = "  "; + hostIcon = " 󰌢 "; + kernelIcon = "  "; + osIcon = "  "; + memoryIcon = "  "; + weatherIcon = "  "; + musicIcon = "  "; + } + + const Color::Palette16 labelColor = Color::Yellow; + const Color::Palette16 valueColor = Color::White; + const Color::Palette16 borderColor = Color::GrayLight; + const Color::Palette16 iconColor = Color::Cyan; Elements content; @@ -111,7 +118,6 @@ namespace { return hbox({ text(icon) | color(iconColor), text(label) | color(labelColor), - text(" "), filler(), text(value) | color(valueColor), text(" "), diff --git a/src/os/linux.cpp b/src/os/linux.cpp index f65fe61..5bcee52 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -1,5 +1,6 @@ #ifdef __linux__ +#include #include #include #include @@ -9,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -51,9 +53,8 @@ namespace { // Find the end of the numeric part const size_t end = view.find_first_not_of("0123456789"); - if (end != std::string_view::npos) { + if (end != std::string_view::npos) view = view.substr(0, end); - } // Get pointers via iterators const char* startPtr = &*view.begin(); // Safe iterator-to-pointer conversion @@ -346,14 +347,81 @@ namespace { std::ifstream cmdline("/proc/self/environ"); std::string envVars((std::istreambuf_iterator(cmdline)), std::istreambuf_iterator()); - for (const auto& [process, deName] : processChecks) { + for (const auto& [process, deName] : processChecks) if (envVars.find(process) != std::string::npos) return deName; - } return "Unknown"; } + fn CountNix() noexcept -> std::optional { + constexpr std::string_view dbPath = "/nix/var/nix/db/db.sqlite"; + constexpr std::string_view querySql = "SELECT COUNT(*) FROM ValidPaths WHERE sigs IS NOT NULL;"; + + sqlite3* sqlDB = nullptr; + sqlite3_stmt* stmt = nullptr; + size_t count = 0; + + // 1. Direct URI construction without string concatenation + const std::string uri = + fmt::format("file:{}{}immutable=1", dbPath, (dbPath.find('?') != std::string_view::npos) ? "&" : "?"); + + // 2. Open database with optimized flags + if (sqlite3_open_v2(uri.c_str(), &sqlDB, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI | SQLITE_OPEN_NOMUTEX, nullptr) != + SQLITE_OK) { + return std::nullopt; + } + + // 3. Configure database for maximum read performance + sqlite3_exec(sqlDB, "PRAGMA journal_mode=OFF; PRAGMA mmap_size=268435456;", nullptr, nullptr, nullptr); + + // 4. Single-step prepared statement execution + if (sqlite3_prepare_v3(sqlDB, querySql.data(), querySql.size(), SQLITE_PREPARE_PERSISTENT, &stmt, nullptr) == + SQLITE_OK) { + if (sqlite3_step(stmt) == SQLITE_ROW) { + count = static_cast(sqlite3_column_int64(stmt, 0)); + } + sqlite3_finalize(stmt); + } + + sqlite3_close(sqlDB); + return count ? std::optional { count } : std::nullopt; + } + + fn CountNixWithCache() noexcept -> std::optional { + constexpr std::string_view dbPath = "/nix/var/nix/db/db.sqlite"; + constexpr std::string_view cachePath = "/tmp/nix_pkg_count.cache"; + + // 1. Check cache validity atomically + try { + const auto dbMtime = std::filesystem::last_write_time(dbPath); + const auto cacheMtime = std::filesystem::last_write_time(cachePath); + + if (std::filesystem::exists(cachePath) && dbMtime <= cacheMtime) { + // Read cached value (atomic read) + std::ifstream cache(cachePath.data(), std::ios::binary); + size_t count = 0; + cache.read(std::bit_cast(&count), sizeof(count)); + return cache ? std::optional(count) : std::nullopt; + } + } catch (...) {} // Ignore errors, fall through to rebuild cache + + // 2. Compute fresh value + const auto count = CountNix(); // Original optimized function + + // 3. Update cache atomically (write+rename pattern) + if (count) { + constexpr std::string_view tmpPath = "/tmp/nix_pkg_count.tmp"; + { + std::ofstream tmp(tmpPath.data(), std::ios::binary | std::ios::trunc); + tmp.write(std::bit_cast(&*count), sizeof(*count)); + } // RAII close + + std::filesystem::rename(tmpPath, cachePath); + } + + return count; + } } fn GetOSVersion() -> std::string { @@ -414,10 +482,33 @@ fn GetNowPlaying() -> string { const std::map, sdbus::Variant>& metadata = metadataVariant.get>(); - auto iter = metadata.find("xesam:title"); + std::string title; + auto titleIter = metadata.find("xesam:title"); + if (titleIter != metadata.end() && titleIter->second.containsValueOfType()) { + title = titleIter->second.get(); + } - if (iter != metadata.end() && iter->second.containsValueOfType()) - return iter->second.get(); + std::string artist; + auto artistIter = metadata.find("xesam:artist"); + if (artistIter != metadata.end() && artistIter->second.containsValueOfType>()) { + auto artists = artistIter->second.get>(); + if (!artists.empty()) { + artist = artists[0]; + } + } + + std::string result; + if (!artist.empty() && !title.empty()) { + result = artist + " - " + title; + } else if (!title.empty()) { + result = title; + } else if (!artist.empty()) { + result = artist; + } else { + result = ""; + } + + return result; } } catch (const sdbus::Error& e) { if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer") { @@ -430,7 +521,6 @@ fn GetNowPlaying() -> string { return ""; } - fn GetWindowManager() -> string { // Check environment variables first const char* xdgSessionType = std::getenv("XDG_SESSION_TYPE"); @@ -505,6 +595,8 @@ fn GetKernelVersion() -> string { return ""; } + DEBUG_LOG("{}", CountNixWithCache().value_or(0)); + return static_cast(uts.release); } diff --git a/src/util/result.h b/src/util/result.h deleted file mode 100644 index bf9dcc0..0000000 --- a/src/util/result.h +++ /dev/null @@ -1,218 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "macros.h" -#include "types.h" - -/** - * @class Error - * @brief Represents an error with a message. - * - * This class is used to encapsulate error messages that can be returned from functions. - */ -class Error { - public: - /** - * @brief Constructs an Error with a message. - * @param message The error message. - */ - explicit Error(string message) : m_Message(std::move(message)) {} - - /** - * @brief Retrieves the error message. - * @return A constant reference to the error message string. - */ - [[nodiscard]] fn message() const -> const string& { return m_Message; } - - private: - string m_Message; ///< The error message. -}; - -// Primary template for Result with a default type of void - -/** - * @class Result - * @brief Represents a result that can either be a value or an error. - * - * This is the primary template for Result, which defaults to handling void results. - */ -template -class Result; - -// Specialization for Result - -/** - * @class Result - * @brief Specialization of Result for handling void results. - * - * This class is used when a function either succeeds with no value or fails with an error. - */ -template <> -class Result { - public: - /** - * @brief Constructs a successful Result. - */ - Result() : m_Result(std::monostate {}) {} - - /** - * @brief Constructs an error Result. - * @param error The error object. - */ - Result(const Error& error) : m_Result(error) {} - - /** - * @brief Constructs an error Result. - * @param error An rvalue reference to the error object. - */ - Result(Error&& error) : m_Result(std::move(error)) {} - - /** - * @brief Checks if the Result is successful. - * @return True if the Result is successful, otherwise false. - */ - [[nodiscard]] fn isOk() const -> bool { return std::holds_alternative(m_Result); } - - /** - * @brief Checks if the Result contains an error. - * @return True if the Result contains an error, otherwise false. - */ - [[nodiscard]] fn isErr() const -> bool { return std::holds_alternative(m_Result); } - - /** - * @brief Throws an exception if the Result contains an error. - * - * This function should be called only if the Result is successful. - */ - void value() const { - if (isErr()) { - throw std::logic_error("Attempted to access value of an error Result"); - } - } - - /** - * @brief Retrieves the error object. - * @return A constant reference to the Error object. - * @throws std::logic_error if the Result is successful. - */ - [[nodiscard]] fn error() const -> const Error& { - if (isOk()) { - throw std::logic_error("Attempted to access error of an ok Result"); - } - return std::get(m_Result); - } - - private: - std::variant - m_Result; ///< The underlying result, which can be either void or an Error. -}; - -// Primary template for Result - -/** - * @class Result - * @brief Represents a result that can either be a value of type T or an error. - * - * This template class is used to handle results that can either be a successful value or an error. - * @tparam T The type of the successful value. - */ -template -class Result { - public: - /** - * @brief Constructs a successful Result with a value. - * @param value The value of the Result. - */ - Result(const T& value) : m_Result(value) {} - - /** - * @brief Constructs a successful Result with a value. - * @param value An rvalue reference to the value. - */ - Result(T&& value) : m_Result(std::move(value)) {} - - /** - * @brief Constructs an error Result. - * @param error The error object. - */ - Result(const Error& error) : m_Result(error) {} - - /** - * @brief Constructs an error Result. - * @param error An rvalue reference to the error object. - */ - Result(Error&& error) : m_Result(std::move(error)) {} - - /** - * @brief Checks if the Result is successful. - * @return True if the Result is successful, otherwise false. - */ - [[nodiscard]] fn isOk() const -> bool { return std::holds_alternative(m_Result); } - - /** - * @brief Checks if the Result contains an error. - * @return True if the Result contains an error, otherwise false. - */ - [[nodiscard]] fn isErr() const -> bool { return std::holds_alternative(m_Result); } - - /** - * @brief Retrieves the value. - * @return A constant reference to the value. - * @throws std::logic_error if the Result contains an error. - */ - fn value() const -> const T& { - if (isErr()) { - throw std::logic_error("Attempted to access value of an error Result"); - } - return std::get(m_Result); - } - - /** - * @brief Retrieves the error object. - * @return A constant reference to the Error object. - * @throws std::logic_error if the Result is successful. - */ - [[nodiscard]] fn error() const -> const Error& { - if (isOk()) { - throw std::logic_error("Attempted to access error of an ok Result"); - } - return std::get(m_Result); - } - - /** - * @brief Retrieves the value or returns a default value. - * @param defaultValue The default value to return if the Result contains an error. - * @return The value if the Result is successful, otherwise the default value. - */ - fn valueOr(const T& defaultValue) const -> T { - return isOk() ? std::get(m_Result) : defaultValue; - } - - private: - std::variant - m_Result; ///< The underlying result, which can be either a value of type T or an Error. -}; - -/** - * @brief Helper function to create a successful Result. - * - * This function deduces the type of the value and creates a successful Result. - * @tparam T The type of the value. - * @param value The value to be stored in the Result. - * @return A Result object containing the value. - */ -template -fn Ok(T&& value) { - return Result>(std::forward(value)); -} - -/** - * @brief Helper function to create a successful Result. - * - * This function creates a successful Result that does not contain a value. - * @return A Result object indicating success. - */ -inline fn Ok() -> Result { return {}; }