From 867bab1050e67974095f95c9fe5881aa69297958 Mon Sep 17 00:00:00 2001 From: Mars Date: Sun, 11 May 2025 00:38:34 -0400 Subject: [PATCH] lots of new stuff waow --- include/argparse.hpp | 6 +- include/matchit.hpp | 2 +- meson.build | 106 +++++----- src/{config/config.cpp => Config/Config.cpp} | 15 +- src/{config/config.hpp => Config/Config.hpp} | 57 +++--- .../system_data.cpp => Core/SystemData.cpp} | 57 +++--- .../system_data.hpp => Core/SystemData.hpp} | 35 ++-- src/{os/os.hpp => OS/OperatingSystem.hpp} | 6 +- src/{os/windows.cpp => OS/Windows.cpp} | 38 ++-- src/{os => OS}/bsd.cpp | 0 src/{os => OS}/haiku.cpp | 0 src/{os => OS}/linux.cpp | 0 src/{os => OS}/macos.cpp | 0 src/{os => OS}/macos/bridge.hpp | 0 src/{os => OS}/macos/bridge.mm | 0 src/{os => OS}/serenity.cpp | 0 .../PackageCounting.cpp} | 18 +- .../PackageCounting.hpp} | 12 +- src/Services/Weather.hpp | 49 +++++ src/Services/Weather/IWeatherService.hpp | 24 +++ src/Services/Weather/OpenMeteoService.cpp | 156 +++++++++++++++ src/Services/Weather/OpenMeteoService.hpp | 16 ++ .../Weather/OpenWeatherMapService.cpp | 184 ++++++++++++++++++ .../Weather/OpenWeatherMapService.hpp | 18 ++ src/{ui/ui.cpp => UI/UI.cpp} | 13 +- src/{ui/ui.hpp => UI/UI.hpp} | 6 +- src/{util/cache.hpp => Util/Caching.hpp} | 14 +- src/{util/defs.hpp => Util/Definitions.hpp} | 0 src/{util/helpers.hpp => Util/Env.hpp} | 16 +- src/{util/error.hpp => Util/Error.hpp} | 14 +- src/{util/logging.hpp => Util/Logging.hpp} | 6 +- src/{util/types.hpp => Util/Types.hpp} | 0 src/config/weather.cpp | 124 ------------ src/config/weather.hpp | 78 -------- src/main.cpp | 86 ++++---- src/wrappers/{dbus.hpp => DBus.hpp} | 0 src/wrappers/{wayland.hpp => Wayland.hpp} | 0 src/wrappers/{xcb.hpp => XCB.hpp} | 0 38 files changed, 704 insertions(+), 452 deletions(-) rename src/{config/config.cpp => Config/Config.cpp} (96%) rename src/{config/config.hpp => Config/Config.hpp} (75%) rename src/{core/system_data.cpp => Core/SystemData.cpp} (53%) rename src/{core/system_data.hpp => Core/SystemData.hpp} (62%) rename src/{os/os.hpp => OS/OperatingSystem.hpp} (97%) rename src/{os/windows.cpp => OS/Windows.cpp} (92%) rename src/{os => OS}/bsd.cpp (100%) rename src/{os => OS}/haiku.cpp (100%) rename src/{os => OS}/linux.cpp (100%) rename src/{os => OS}/macos.cpp (100%) rename src/{os => OS}/macos/bridge.hpp (100%) rename src/{os => OS}/macos/bridge.mm (100%) rename src/{os => OS}/serenity.cpp (100%) rename src/{core/package.cpp => Services/PackageCounting.cpp} (98%) rename src/{core/package.hpp => Services/PackageCounting.hpp} (95%) create mode 100644 src/Services/Weather.hpp create mode 100644 src/Services/Weather/IWeatherService.hpp create mode 100644 src/Services/Weather/OpenMeteoService.cpp create mode 100644 src/Services/Weather/OpenMeteoService.hpp create mode 100644 src/Services/Weather/OpenWeatherMapService.cpp create mode 100644 src/Services/Weather/OpenWeatherMapService.hpp rename src/{ui/ui.cpp => UI/UI.cpp} (96%) rename src/{ui/ui.hpp => UI/UI.hpp} (92%) rename src/{util/cache.hpp => Util/Caching.hpp} (96%) rename src/{util/defs.hpp => Util/Definitions.hpp} (100%) rename src/{util/helpers.hpp => Util/Env.hpp} (81%) rename src/{util/error.hpp => Util/Error.hpp} (96%) rename src/{util/logging.hpp => Util/Logging.hpp} (99%) rename src/{util/types.hpp => Util/Types.hpp} (100%) delete mode 100644 src/config/weather.cpp delete mode 100644 src/config/weather.hpp rename src/wrappers/{dbus.hpp => DBus.hpp} (100%) rename src/wrappers/{wayland.hpp => Wayland.hpp} (100%) rename src/wrappers/{xcb.hpp => XCB.hpp} (100%) diff --git a/include/argparse.hpp b/include/argparse.hpp index 7662bae..3696f62 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -73,9 +73,9 @@ #include #endif -#include "src/util/defs.hpp" -#include "src/util/error.hpp" -#include "src/util/types.hpp" +#include "Util/Definitions.hpp" +#include "Util/Error.hpp" +#include "Util/Types.hpp" #ifndef ARGPARSE_CUSTOM_STRTOF #define ARGPARSE_CUSTOM_STRTOF strtof diff --git a/include/matchit.hpp b/include/matchit.hpp index cea514a..bc60415 100644 --- a/include/matchit.hpp +++ b/include/matchit.hpp @@ -17,7 +17,7 @@ #include #include -#include "src/util/defs.hpp" +#include "Util/Definitions.hpp" // NOLINTBEGIN(readability-identifier-*, cppcoreguidelines-special-member-functions) namespace matchit { diff --git a/meson.build b/meson.build index de36128..356fad0 100644 --- a/meson.build +++ b/meson.build @@ -4,8 +4,8 @@ project( 'draconis++', 'cpp', - version: '0.1.0', - default_options: [ + version : '0.1.0', + default_options : [ 'default_library=static', 'buildtype=debugoptimized', 'b_vscrt=mt', @@ -35,13 +35,13 @@ common_warning_flags = [ ] common_cpp_flags = { - 'common': [ + 'common' : [ '-fno-strict-enums', '-fvisibility=hidden', '-fvisibility-inlines-hidden', '-std=c++26', ], - 'msvc': [ + 'msvc' : [ '-DNOMINMAX', '/MT', '/Zc:__cplusplus', '/Zc:preprocessor', @@ -49,20 +49,20 @@ common_cpp_flags = { '/external:anglebrackets', '/std:c++latest', ], - 'unix_extra': '-march=native', - 'windows_extra': '-DCURL_STATICLIB', + 'unix_extra' : '-march=native', + 'windows_extra' : '-DCURL_STATICLIB', } # Configure Objective-C++ for macOS if host_system == 'darwin' - add_languages('objcpp', native: false) + add_languages('objcpp', native : false) objcpp = meson.get_compiler('objcpp') objcpp_flags = common_warning_flags + [ '-std=c++26', '-fvisibility=hidden', '-fvisibility-inlines-hidden', ] - add_project_arguments(objcpp.get_supported_arguments(objcpp_flags), language: 'objcpp') + add_project_arguments(objcpp.get_supported_arguments(objcpp_flags), language : 'objcpp') endif # Apply C++ compiler arguments @@ -80,29 +80,36 @@ else endif endif -add_project_arguments(common_cpp_args, language: 'cpp') +add_project_arguments(common_cpp_args, language : 'cpp') + +# --------------------- # +# Include Directories # +# --------------------- # +project_internal_includes = include_directories('src') +project_public_includes = include_directories('include', is_system : true) # ------- # # Files # # ------- # base_sources = files( - 'src/config/config.cpp', - 'src/config/weather.cpp', - 'src/core/package.cpp', - 'src/core/system_data.cpp', + 'src/Config/Config.cpp', + 'src/Core/SystemData.cpp', + 'src/Services/Weather/OpenMeteoService.cpp', + 'src/Services/Weather/OpenWeatherMapService.cpp', + 'src/Services/PackageCounting.cpp', + 'src/UI/UI.cpp', 'src/main.cpp', - 'src/ui/ui.cpp', ) platform_sources = { - 'darwin': ['src/os/macos.cpp', 'src/os/macos/bridge.mm'], - 'dragonfly': ['src/os/bsd.cpp'], - 'freebsd': ['src/os/bsd.cpp'], - 'haiku': ['src/os/haiku.cpp'], - 'linux': ['src/os/linux.cpp'], - 'netbsd': ['src/os/bsd.cpp'], - 'serenity': ['src/os/serenity.cpp'], - 'windows': ['src/os/windows.cpp'], + 'darwin' : ['src/OS/macOS.cpp', 'src/OS/macOS/bridge.mm'], + 'dragonfly' : ['src/OS/bsd.cpp'], + 'freebsd' : ['src/OS/bsd.cpp'], + 'haiku' : ['src/OS/haiku.cpp'], + 'linux' : ['src/OS/linux.cpp'], + 'netbsd' : ['src/OS/bsd.cpp'], + 'serenity' : ['src/OS/serenity.cpp'], + 'windows' : ['src/OS/Windows.cpp'], } sources = base_sources + files(platform_sources.get(host_system, [])) @@ -111,9 +118,9 @@ sources = base_sources + files(platform_sources.get(host_system, [])) # Dependencies Config # # --------------------- # common_deps = [ - dependency('libcurl', include_type: 'system', static: true), - dependency('tomlplusplus', include_type: 'system', static: true), - dependency('openssl', include_type: 'system', static: true, required: false), + dependency('libcurl', include_type : 'system', static : true), + dependency('tomlplusplus', include_type : 'system', static : true), + dependency('openssl', include_type : 'system', static : true, required : false), ] # Platform-specific dependencies @@ -124,8 +131,8 @@ if host_system == 'darwin' dependency('SQLiteCpp'), dependency( 'appleframeworks', - modules: ['foundation', 'mediaplayer', 'systemconfiguration'], - static: true, + modules : ['foundation', 'mediaplayer', 'systemconfiguration'], + static : true, ), dependency('iconv'), ] @@ -136,11 +143,11 @@ elif host_system == 'windows' ] elif host_system != 'serenity' and host_system != 'haiku' # Make dbus, x11, and wayland dependencies optional - dbus_dep = dependency('dbus-1', required: false) - xcb_dep = dependency('xcb', required: false) - xau_dep = dependency('xau', required: false) - xdmcp_dep = dependency('xdmcp', required: false) - wayland_dep = dependency('wayland-client', required: false) + dbus_dep = dependency('dbus-1', required : false) + xcb_dep = dependency('xcb', required : false) + xau_dep = dependency('xau', required : false) + xdmcp_dep = dependency('xdmcp', required : false) + wayland_dep = dependency('wayland-client', required : false) platform_deps += [ dependency('SQLiteCpp'), @@ -149,15 +156,15 @@ elif host_system != 'serenity' and host_system != 'haiku' if dbus_dep.found() platform_deps += dbus_dep - add_project_arguments('-DHAVE_DBUS', language: 'cpp') + add_project_arguments('-DHAVE_DBUS', language : 'cpp') endif if xcb_dep.found() and xau_dep.found() and xdmcp_dep.found() platform_deps += [xcb_dep, xau_dep, xdmcp_dep] - add_project_arguments('-DHAVE_XCB', language: 'cpp') + add_project_arguments('-DHAVE_XCB', language : 'cpp') endif if wayland_dep.found() platform_deps += wayland_dep - add_project_arguments('-DHAVE_WAYLAND', language: 'cpp') + add_project_arguments('-DHAVE_WAYLAND', language : 'cpp') endif endif @@ -165,28 +172,28 @@ endif ftxui_components = ['ftxui::screen', 'ftxui::dom', 'ftxui::component'] ftxui_dep = dependency( 'ftxui', - modules: ftxui_components, - include_type: 'system', - static: true, - required: false, + modules : ftxui_components, + include_type : 'system', + static : true, + required : false, ) if not ftxui_dep.found() ftxui_dep = declare_dependency( - dependencies: [ - dependency('ftxui-dom', fallback: ['ftxui', 'dom_dep']), - dependency('ftxui-screen', fallback: ['ftxui', 'screen_dep']), - dependency('ftxui-component', fallback: ['ftxui', 'component_dep']), + dependencies : [ + dependency('ftxui-dom', fallback : ['ftxui', 'dom_dep']), + dependency('ftxui-screen', fallback : ['ftxui', 'screen_dep']), + dependency('ftxui-component', fallback : ['ftxui', 'component_dep']), ], ) endif -glaze_dep = dependency('glaze', include_type: 'system', required: false) +glaze_dep = dependency('glaze', include_type : 'system', required : false) if not glaze_dep.found() cmake = import('cmake') glaze_proj = cmake.subproject('glaze') - glaze_dep = glaze_proj.dependency('glaze_glaze', include_type: 'system') + glaze_dep = glaze_proj.dependency('glaze_glaze', include_type : 'system') endif # Combine all dependencies @@ -212,8 +219,9 @@ endif executable( 'draconis++', sources, - objc_args: objc_args, - link_args: link_args, - dependencies: deps, - install: true, + include_directories : [project_internal_includes, project_public_includes], + objc_args : objc_args, + link_args : link_args, + dependencies : deps, + install : true, ) \ No newline at end of file diff --git a/src/config/config.cpp b/src/Config/Config.cpp similarity index 96% rename from src/config/config.cpp rename to src/Config/Config.cpp index 9b00b46..6368196 100644 --- a/src/config/config.cpp +++ b/src/Config/Config.cpp @@ -1,4 +1,4 @@ -#include "config.hpp" +#include "Config.hpp" #include // std::filesystem::{path, operator/, exists, create_directories} #include // std::{format, format_error} @@ -8,15 +8,10 @@ #include // toml::{parse_file, parse_result} #include // toml::table -#ifndef _WIN32 - #include // passwd, getpwuid - #include // getuid -#endif - -#include "src/util/defs.hpp" -#include "src/util/helpers.hpp" -#include "src/util/logging.hpp" -#include "src/util/types.hpp" +#include "Util/Definitions.hpp" +#include "Util/Env.hpp" +#include "Util/Logging.hpp" +#include "Util/Types.hpp" namespace fs = std::filesystem; diff --git a/src/config/config.hpp b/src/Config/Config.hpp similarity index 75% rename from src/config/config.hpp rename to src/Config/Config.hpp index e7c01db..b4aa943 100644 --- a/src/config/config.hpp +++ b/src/Config/Config.hpp @@ -1,5 +1,6 @@ #pragma once +#include // std::{make_unique, unique_ptr} #include // toml::node #include // toml::node_view #include // toml::table @@ -11,15 +12,16 @@ #include // getpwuid, passwd #include // getuid - #include "src/util/helpers.hpp" + #include "Util/Env.hpp" #endif -#include "src/util/defs.hpp" -#include "src/util/error.hpp" -#include "src/util/logging.hpp" -#include "src/util/types.hpp" - -#include "weather.hpp" +#include "../Services/Weather/OpenMeteoService.hpp" +#include "../Services/Weather/OpenWeatherMapService.hpp" +#include "Services/Weather.hpp" +#include "Util/Definitions.hpp" +#include "Util/Error.hpp" +#include "Util/Logging.hpp" +#include "Util/Types.hpp" using util::error::DracError; using util::types::CStr, util::types::String, util::types::Array, util::types::Option, util::types::Result; @@ -103,8 +105,9 @@ struct Weather { String apiKey; ///< API key for the weather service. String units; ///< Units for temperature, either "metric" or "imperial". - bool enabled = false; ///< Flag to enable or disable the Weather feature. - bool showTownName = false; ///< Flag to show the town name in the output. + bool enabled = false; ///< Flag to enable or disable the Weather feature. + bool showTownName = false; ///< Flag to show the town name in the output. + std::unique_ptr service = nullptr; ///< Pointer to the weather service. /** * @brief Parses a TOML table to create a Weather instance. @@ -112,11 +115,9 @@ struct Weather { * @return A Weather instance with the parsed values, or defaults otherwise. */ static fn fromToml(const toml::table& tbl) -> Weather { - Weather weather; - + Weather weather; const Option apiKey = tbl["api_key"].value(); - - weather.enabled = tbl["enabled"].value_or(false) && apiKey; + weather.enabled = tbl["enabled"].value_or(false) && apiKey; if (!weather.enabled) return weather; @@ -125,6 +126,9 @@ struct Weather { weather.showTownName = tbl["show_town_name"].value_or(false); weather.units = tbl["units"].value_or("metric"); + // Read provider (default to "openweathermap" if not set) + String provider = tbl["provider"].value_or("openweathermap"); + if (const toml::node_view location = tbl["location"]) { if (location.is_string()) weather.location = *location.value(); @@ -137,20 +141,27 @@ struct Weather { error_log("Invalid location format in config."); weather.enabled = false; } + } else { + error_log("No location provided in config."); + weather.enabled = false; + } + + if (weather.enabled) { + if (provider == "openmeteo") { + if (std::holds_alternative(weather.location)) { + const auto& coords = std::get(weather.location); + weather.service = std::make_unique(coords.lat, coords.lon, weather.units); + } else { + error_log("OpenMeteo requires coordinates for location."); + weather.enabled = false; + } + } else { + weather.service = std::make_unique(weather.location, weather.apiKey, weather.units); + } } return weather; } - - /** - * @brief Retrieves the weather information based on the configuration. - * @return The weather information as a WeatherOutput object. - * - * This function fetches the weather data based on the configured location, - * API key, and units. It returns a WeatherOutput object containing the - * retrieved weather data. - */ - [[nodiscard]] fn getWeatherInfo() const -> Result; }; /** diff --git a/src/core/system_data.cpp b/src/Core/SystemData.cpp similarity index 53% rename from src/core/system_data.cpp rename to src/Core/SystemData.cpp index 670b1ee..112ab71 100644 --- a/src/core/system_data.cpp +++ b/src/Core/SystemData.cpp @@ -1,19 +1,18 @@ -#include "system_data.hpp" +#include "SystemData.hpp" -#include // std::chrono::system_clock -#include // localtime_r/s, strftime, time_t, tm -#include // std::format -#include // std::{async, launch} +#include // std::chrono::system_clock +#include // localtime_r/s, strftime, time_t, tm +#include // std::format +#include // std::{async, launch} +#include // matchit::{match, is, in, _} -#include "src/config/config.hpp" -#include "src/config/weather.hpp" -#include "src/core/package.hpp" -#include "src/os/os.hpp" -#include "src/util/defs.hpp" -#include "src/util/error.hpp" -#include "src/util/types.hpp" - -#include "include/matchit.hpp" +#include "Config/Config.hpp" +#include "OS/OperatingSystem.hpp" +#include "Services/PackageCounting.hpp" +#include "Services/Weather.hpp" +#include "Util/Definitions.hpp" +#include "Util/Error.hpp" +#include "Util/Types.hpp" using util::error::DracError, util::error::DracErrorCode; @@ -73,18 +72,20 @@ namespace os { using package::GetTotalCount; using util::types::Future, util::types::Err; - Future> hostFut = std::async(async, GetHost); - Future> kernelFut = std::async(async, GetKernelVersion); - Future> osFut = std::async(async, GetOSVersion); - Future> memFut = std::async(async, GetMemInfo); - Future> deFut = std::async(async, GetDesktopEnvironment); - Future> wmFut = std::async(async, GetWindowManager); - Future> diskFut = std::async(async, GetDiskUsage); - Future> shellFut = std::async(async, GetShell); - Future> pkgFut = std::async(async, GetTotalCount); - Future> npFut = std::async(config.nowPlaying.enabled ? async : deferred, GetNowPlaying); - Future> wthrFut = std::async(config.weather.enabled ? async : deferred, [&config] { - return config.weather.getWeatherInfo(); + Future> hostFut = std::async(async, GetHost); + Future> kernelFut = std::async(async, GetKernelVersion); + Future> osFut = std::async(async, GetOSVersion); + Future> memFut = std::async(async, GetMemInfo); + Future> deFut = std::async(async, GetDesktopEnvironment); + Future> wmFut = std::async(async, GetWindowManager); + Future> diskFut = std::async(async, GetDiskUsage); + Future> shellFut = std::async(async, GetShell); + Future> pkgFut = std::async(async, GetTotalCount); + Future> npFut = std::async(config.nowPlaying.enabled ? async : deferred, GetNowPlaying); + Future> wthrFut = std::async(config.weather.enabled ? async : deferred, [&config]() -> Result { + return config.weather.enabled && config.weather.service + ? config.weather.service->getWeatherInfo() + : Err(DracError(util::error::DracErrorCode::ApiUnavailable, "Weather API disabled")); }); { @@ -100,8 +101,8 @@ namespace os { this->diskUsage = diskFut.get(); this->shell = shellFut.get(); this->packageCount = pkgFut.get(); - this->weather = config.weather.enabled ? wthrFut.get() : Err(DracError(ApiUnavailable, "Weather API disabled")); - this->nowPlaying = config.nowPlaying.enabled ? npFut.get() : Err(DracError(ApiUnavailable, "Now Playing API disabled")); + this->weather = config.weather.enabled ? wthrFut.get() : Err(DracError(util::error::DracErrorCode::ApiUnavailable, "Weather API disabled")); + this->nowPlaying = config.nowPlaying.enabled ? npFut.get() : Err(DracError(util::error::DracErrorCode::ApiUnavailable, "Now Playing API disabled")); } } } // namespace os diff --git a/src/core/system_data.hpp b/src/Core/SystemData.hpp similarity index 62% rename from src/core/system_data.hpp rename to src/Core/SystemData.hpp index ecbbb23..d7ae4ec 100644 --- a/src/core/system_data.hpp +++ b/src/Core/SystemData.hpp @@ -2,10 +2,10 @@ #include // std::{formatter, format_to} -#include "src/config/config.hpp" -#include "src/config/weather.hpp" -#include "src/util/defs.hpp" -#include "src/util/types.hpp" +#include "Services/Weather.hpp" +#include "Util/Definitions.hpp" +#include "Util/Error.hpp" +#include "Util/Types.hpp" struct Config; @@ -26,7 +26,8 @@ struct BytesToGiB { * @brief Constructor for BytesToGiB. * @param value The byte value to be converted. */ - explicit constexpr BytesToGiB(u64 value) : value(value) {} + explicit constexpr BytesToGiB(u64 value) + : value(value) {} }; /// @brief Conversion factor from bytes to GiB @@ -72,18 +73,18 @@ namespace os { * in order to display it at all at once during runtime. */ struct SystemData { - Result date; ///< Current date (e.g., "April 26th"). - Result host; ///< Host/product family (e.g., "MacBook Air"). - Result kernelVersion; ///< OS kernel version (e.g., "6.14.4"). - Result osVersion; ///< OS pretty name (e.g., "Ubuntu 24.04.2 LTS"). - Result memInfo; ///< Total physical RAM in bytes. - Result desktopEnv; ///< Desktop environment (e.g., "KDE"). - Result windowMgr; ///< Window manager (e.g., "KWin"). - Result diskUsage; ///< Used/Total disk space for root filesystem. - Result shell; ///< Name of the current user shell (e.g., "zsh"). - Result packageCount; ///< Total number of packages installed. - Result nowPlaying; ///< Result of fetching media info. - Result weather; ///< Result of fetching weather info. + Result date; ///< Current date (e.g., "April 26th"). + Result host; ///< Host/product family (e.g., "MacBook Air"). + Result kernelVersion; ///< OS kernel version (e.g., "6.14.4"). + Result osVersion; ///< OS pretty name (e.g., "Ubuntu 24.04.2 LTS"). + Result memInfo; ///< Total physical RAM in bytes. + Result desktopEnv; ///< Desktop environment (e.g., "KDE"). + Result windowMgr; ///< Window manager (e.g., "KWin"). + Result diskUsage; ///< Used/Total disk space for root filesystem. + Result shell; ///< Name of the current user shell (e.g., "zsh"). + Result packageCount; ///< Total number of packages installed. + Result nowPlaying; ///< Result of fetching media info. + Result weather; ///< Result of fetching weather info. /** * @brief Constructs a SystemData object and initializes its members. diff --git a/src/os/os.hpp b/src/OS/OperatingSystem.hpp similarity index 97% rename from src/os/os.hpp rename to src/OS/OperatingSystem.hpp index 86998ff..ef2406b 100644 --- a/src/os/os.hpp +++ b/src/OS/OperatingSystem.hpp @@ -1,8 +1,8 @@ #pragma once -#include "src/util/defs.hpp" -#include "src/util/error.hpp" -#include "src/util/types.hpp" +#include "Util/Definitions.hpp" +#include "Util/Error.hpp" +#include "Util/Types.hpp" /** * @namespace os diff --git a/src/os/windows.cpp b/src/OS/Windows.cpp similarity index 92% rename from src/os/windows.cpp rename to src/OS/Windows.cpp index fbb7363..22faf5f 100644 --- a/src/os/windows.cpp +++ b/src/OS/Windows.cpp @@ -11,18 +11,15 @@ #include #include #include -#include #include -#include -#include -#include "src/core/package.hpp" -#include "src/util/error.hpp" -#include "src/util/helpers.hpp" -#include "src/util/logging.hpp" -#include "src/util/types.hpp" +#include "Services/PackageCounting.hpp" +#include "Util/Env.hpp" +#include "Util/Error.hpp" +#include "Util/Logging.hpp" +#include "Util/Types.hpp" -#include "os.hpp" +#include "OperatingSystem.hpp" // clang-format on using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW); @@ -65,8 +62,7 @@ namespace { String value((type == REG_SZ || type == REG_EXPAND_SZ) ? dataSize - 1 : dataSize, '\0'); // NOLINTNEXTLINE(*-pro-type-reinterpret-cast) - required here - if (RegQueryValueExA(key, valueName.c_str(), nullptr, nullptr, reinterpret_cast(value.data()), &dataSize) != - ERROR_SUCCESS) { + if (RegQueryValueExA(key, valueName.c_str(), nullptr, nullptr, reinterpret_cast(value.data()), &dataSize) != ERROR_SUCCESS) { RegCloseKey(key); return ""; } @@ -175,10 +171,7 @@ namespace os { if (GlobalMemoryStatusEx(&memInfo)) return memInfo.ullTotalPhys; - DWORD lastError = GetLastError(); - return Err(DracError( - DracErrorCode::PlatformSpecific, std::format("GlobalMemoryStatusEx failed with error code {}", lastError) - )); + return Err(DracError(DracErrorCode::PlatformSpecific, std::format("GlobalMemoryStatusEx failed with error code {}", GetLastError()))); } fn GetNowPlaying() -> Result { @@ -216,18 +209,15 @@ namespace os { if (const Option buildNumberOpt = GetBuildNumber()) { if (const u64 buildNumber = *buildNumberOpt; buildNumber >= 22000) { if (const size_t pos = productName.find("Windows 10"); pos != String::npos) { - const bool startBoundary = (pos == 0 || !isalnum(static_cast(productName[pos - 1]))); - const bool endBoundary = - (pos + 10 == productName.length() || !isalnum(static_cast(productName[pos + 10]))); + const bool startBoundary = (pos == 0 || !isalnum(static_cast(productName[pos - 1]))); + const bool endBoundary = (pos + 10 == productName.length() || !isalnum(static_cast(productName[pos + 10]))); - if (startBoundary && endBoundary) { + if (startBoundary && endBoundary) productName.replace(pos, 10, "Windows 11"); - } } } - } else { + } else debug_log("Warning: Could not get build number via WinRT; Win11 detection might be inaccurate."); - } return displayVersion.empty() ? productName : productName + " " + displayVersion; } catch (const std::exception& e) { return Err(DracError(e)); } @@ -245,9 +235,7 @@ namespace os { osInfo.dwOSVersionInfoSize = sizeof(osInfo); if (rtlGetVersion(&osInfo) == 0) - return std::format( - "{}.{}.{}.{}", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwPlatformId - ); + return std::format("{}.{}.{}.{}", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwPlatformId); } } diff --git a/src/os/bsd.cpp b/src/OS/bsd.cpp similarity index 100% rename from src/os/bsd.cpp rename to src/OS/bsd.cpp diff --git a/src/os/haiku.cpp b/src/OS/haiku.cpp similarity index 100% rename from src/os/haiku.cpp rename to src/OS/haiku.cpp diff --git a/src/os/linux.cpp b/src/OS/linux.cpp similarity index 100% rename from src/os/linux.cpp rename to src/OS/linux.cpp diff --git a/src/os/macos.cpp b/src/OS/macos.cpp similarity index 100% rename from src/os/macos.cpp rename to src/OS/macos.cpp diff --git a/src/os/macos/bridge.hpp b/src/OS/macos/bridge.hpp similarity index 100% rename from src/os/macos/bridge.hpp rename to src/OS/macos/bridge.hpp diff --git a/src/os/macos/bridge.mm b/src/OS/macos/bridge.mm similarity index 100% rename from src/os/macos/bridge.mm rename to src/OS/macos/bridge.mm diff --git a/src/os/serenity.cpp b/src/OS/serenity.cpp similarity index 100% rename from src/os/serenity.cpp rename to src/OS/serenity.cpp diff --git a/src/core/package.cpp b/src/Services/PackageCounting.cpp similarity index 98% rename from src/core/package.cpp rename to src/Services/PackageCounting.cpp index 2bfbe4c..80d8284 100644 --- a/src/core/package.cpp +++ b/src/Services/PackageCounting.cpp @@ -1,4 +1,4 @@ -#include "package.hpp" +#include "PackageCounting.hpp" #if !defined(__serenity__) && !defined(_WIN32) #include // SQLite::{Database, OPEN_READONLY} @@ -14,15 +14,14 @@ #include // std::filesystem #include // std::format #include // std::{async, future, launch} +#include // matchit::{match, is, or_, _} #include // std::{errc, 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" - -#include "include/matchit.hpp" +#include "Util/Caching.hpp" +#include "Util/Env.hpp" +#include "Util/Error.hpp" +#include "Util/Logging.hpp" +#include "Util/Types.hpp" namespace { namespace fs = std::filesystem; @@ -454,12 +453,11 @@ namespace package { totalCount += *result; oneSucceeded = true; debug_log("Added {} packages. Current total: {}", *result, totalCount); - } else { + } else match(result.error().code)( is | or_(NotFound, ApiUnavailable, NotSupported) = [&] -> void { debug_at(result.error()); }, is | _ = [&] -> void { error_at(result.error()); } ); - } } catch (const Exception& exc) { error_log("Caught exception while getting package count future: {}", exc.what()); } catch (...) { error_log("Caught unknown exception while getting package count future."); } diff --git a/src/core/package.hpp b/src/Services/PackageCounting.hpp similarity index 95% rename from src/core/package.hpp rename to src/Services/PackageCounting.hpp index d43aabd..fc86713 100644 --- a/src/core/package.hpp +++ b/src/Services/PackageCounting.hpp @@ -4,9 +4,9 @@ #include // glz::object #include // glz::detail::Object -#include "src/util/defs.hpp" -#include "src/util/error.hpp" -#include "src/util/types.hpp" +#include "Util/Definitions.hpp" +#include "Util/Error.hpp" +#include "Util/Types.hpp" namespace package { namespace fs = std::filesystem; @@ -22,7 +22,8 @@ namespace package { i64 timestampEpochSeconds {}; PkgCountCacheData() = default; - PkgCountCacheData(u64 count, i64 timestampEpochSeconds) : count(count), timestampEpochSeconds(timestampEpochSeconds) {} + PkgCountCacheData(u64 count, i64 timestampEpochSeconds) + : count(count), timestampEpochSeconds(timestampEpochSeconds) {} // NOLINTBEGIN(readability-identifier-naming) struct [[maybe_unused]] glaze { @@ -132,8 +133,7 @@ namespace package { fn GetSerenityCount() -> Result; #endif - // Common (potentially cross-platform) -#ifndef _WIN32 +#if defined(__linux__) || defined(__APPLE__) fn CountNix() -> Result; #endif fn CountCargo() -> Result; diff --git a/src/Services/Weather.hpp b/src/Services/Weather.hpp new file mode 100644 index 0000000..6b85fed --- /dev/null +++ b/src/Services/Weather.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include // object +#include // Object + +#include "Util/Types.hpp" + +namespace weather { + using glz::detail::Object, glz::object; + using util::types::String, util::types::Vec, util::types::f64, util::types::usize; + + // NOLINTBEGIN(readability-identifier-naming) - Needs to specifically use `glaze` + /** + * @struct WeatherReport + * @brief Represents a weather report. + * + * Contains temperature, conditions, and timestamp. + */ + struct WeatherReport { + f64 temperature; ///< Degrees (C/F) + util::types::Option name; ///< Optional town/city name (may be missing for some providers) + String description; ///< Weather description (e.g., "clear sky", "rain") + usize timestamp; ///< Seconds since epoch + + /** + * @brief Glaze serialization and deserialization for WeatherReport. + */ + struct [[maybe_unused]] glaze { + using T = WeatherReport; + + static constexpr Object value = object( + "temperature", + &T::temperature, + "name", + &T::name, + "description", + &T::description, + "timestamp", + &T::timestamp + ); + }; + }; + + struct Coords { + f64 lat; + f64 lon; + }; + // NOLINTEND(readability-identifier-naming) +} // namespace weather diff --git a/src/Services/Weather/IWeatherService.hpp b/src/Services/Weather/IWeatherService.hpp new file mode 100644 index 0000000..250de61 --- /dev/null +++ b/src/Services/Weather/IWeatherService.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "Services/Weather.hpp" +#include "Util/Error.hpp" + +namespace weather { + using util::types::Result; + + class IWeatherService { + public: + IWeatherService(const IWeatherService&) = delete; + IWeatherService(IWeatherService&&) = delete; + + fn operator=(const IWeatherService&)->IWeatherService& = delete; + fn operator=(IWeatherService&&)->IWeatherService& = delete; + + virtual ~IWeatherService() = default; + + [[nodiscard]] virtual fn getWeatherInfo() const -> Result = 0; + + protected: + IWeatherService() = default; + }; +} // namespace weather \ No newline at end of file diff --git a/src/Services/Weather/OpenMeteoService.cpp b/src/Services/Weather/OpenMeteoService.cpp new file mode 100644 index 0000000..2933ca4 --- /dev/null +++ b/src/Services/Weather/OpenMeteoService.cpp @@ -0,0 +1,156 @@ +#define NOMINMAX + +#include "OpenMeteoService.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "Util/Caching.hpp" +#include "Util/Error.hpp" +#include "Util/Types.hpp" + +using weather::OpenMeteoService; +using weather::WeatherReport; + +namespace weather { + using util::types::f64, util::types::i32, util::types::String; + + struct OpenMeteoResponse { + struct CurrentWeather { + f64 temperature; + i32 weathercode; + String time; + } currentWeather; + }; + + struct OpenMeteoGlaze { + using T = OpenMeteoResponse; + + // clang-format off + static constexpr auto value = glz::object( + "current_weather", &T::currentWeather + ); + // clang-format on + }; + + struct CurrentWeatherGlaze { + using T = OpenMeteoResponse::CurrentWeather; + + // clang-format off + static constexpr auto value = glz::object( + "temperature", &T::temperature, + "weathercode", &T::weathercode, + "time", &T::time + ); + // clang-format on + }; +} // namespace weather + +template <> +struct glz::meta : weather::OpenMeteoGlaze {}; + +template <> +struct glz::meta : weather::CurrentWeatherGlaze {}; + +namespace { + using glz::opts; + using util::error::DracError, util::error::DracErrorCode; + using util::types::usize, util::types::Err; + + constexpr opts glazeOpts = { .error_on_unknown_keys = false }; + + fn WriteCallback(void* contents, size_t size, size_t nmemb, std::string* str) -> size_t { + size_t totalSize = size * nmemb; + str->append(static_cast(contents), totalSize); + return totalSize; + } + + fn parse_iso8601_to_epoch(const std::string& iso8601) -> usize { + std::tm time = {}; + std::istringstream stream(iso8601); + stream >> std::get_time(&time, "%Y-%m-%dT%H:%M"); + if (stream.fail()) + return 0; +#ifdef _WIN32 + return static_cast(_mkgmtime(&time)); +#else + return static_cast(timegm(&tm)); +#endif + } +} // namespace + +OpenMeteoService::OpenMeteoService(double lat, double lon, std::string units) + : m_lat(lat), m_lon(lon), m_units(std::move(units)) {} + +fn OpenMeteoService::getWeatherInfo() const -> util::types::Result { + using glz::error_ctx, glz::error_code, glz::read, glz::format_error; + using util::cache::ReadCache, util::cache::WriteCache; + using util::types::Array, util::types::String, util::types::Result, util::types::None; + + if (Result data = ReadCache("weather")) { + using std::chrono::system_clock, std::chrono::minutes, std::chrono::seconds; + + const WeatherReport& dataVal = *data; + + if (const auto cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.timestamp)); cacheAge < minutes(60)) + return dataVal; + } + + String url = std::format( + "https://api.open-meteo.com/v1/forecast?latitude={:.4f}&longitude={:.4f}¤t_weather=true&temperature_unit={}", + m_lat, + m_lon, + m_units == "imperial" ? "fahrenheit" : "celsius" + ); + + CURL* curl = curl_easy_init(); + if (!curl) + return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to initialize cURL")); + + String responseBuffer; + 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); + + CURLcode res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + if (res != CURLE_OK) + return Err(DracError(DracErrorCode::ApiUnavailable, std::format("cURL error: {}", curl_easy_strerror(res)))); + + OpenMeteoResponse apiResp {}; + + if (error_ctx errc = read(apiResp, responseBuffer); errc) + return Err(DracError(DracErrorCode::ParseError, "Failed to parse Open-Meteo JSON response")); + + static constexpr Array CODE_DESC = { + "clear sky", + "mainly clear", + "partly cloudy", + "overcast", + "fog", + "drizzle", + "rain", + "snow", + "thunderstorm" + }; + + WeatherReport out = { + .temperature = apiResp.currentWeather.temperature, + .name = None, + .description = CODE_DESC.at(apiResp.currentWeather.weathercode), + .timestamp = parse_iso8601_to_epoch(apiResp.currentWeather.time), + }; + + if (Result<> writeResult = WriteCache("weather", out); !writeResult) + return Err(writeResult.error()); + + return out; +} \ No newline at end of file diff --git a/src/Services/Weather/OpenMeteoService.hpp b/src/Services/Weather/OpenMeteoService.hpp new file mode 100644 index 0000000..58db75a --- /dev/null +++ b/src/Services/Weather/OpenMeteoService.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "IWeatherService.hpp" + +namespace weather { + class OpenMeteoService : public IWeatherService { + public: + OpenMeteoService(f64 lat, f64 lon, String units = "metric"); + [[nodiscard]] fn getWeatherInfo() const -> Result override; + + private: + double m_lat; + double m_lon; + String m_units; + }; +} // namespace weather \ No newline at end of file diff --git a/src/Services/Weather/OpenWeatherMapService.cpp b/src/Services/Weather/OpenWeatherMapService.cpp new file mode 100644 index 0000000..b697685 --- /dev/null +++ b/src/Services/Weather/OpenWeatherMapService.cpp @@ -0,0 +1,184 @@ +#define NOMINMAX + +#include "OpenWeatherMapService.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Util/Caching.hpp" +#include "Util/Error.hpp" +#include "Util/Logging.hpp" +#include "Util/Types.hpp" + +using weather::OpenWeatherMapService; +using weather::WeatherReport; + +namespace weather { + using util::types::f64, util::types::i64, util::types::String, util::types::Vec; + + struct OWMResponse { + struct Main { + f64 temp; + } main; + + struct Weather { + String description; + }; + + Vec weather; + String name; + i64 dt; + }; + + struct OWMMainGlaze { + using T = OWMResponse::Main; + + static constexpr auto value = glz::object("temp", &T::temp); + }; + + struct OWMWeatherGlaze { + using T = OWMResponse::Weather; + + static constexpr auto value = glz::object("description", &T::description); + }; + + struct OWMResponseGlaze { + using T = OWMResponse; + + // clang-format off + static constexpr auto value = glz::object( + "main", &T::main, + "weather", &T::weather, + "name", &T::name, + "dt", &T::dt + ); + // clang-format on + }; +} // namespace weather + +template <> +struct glz::meta : weather::OWMMainGlaze {}; + +template <> +struct glz::meta : weather::OWMWeatherGlaze {}; + +template <> +struct glz::meta : weather::OWMResponseGlaze {}; + +namespace { + using glz::opts, glz::error_ctx, glz::error_code, glz::read, glz::format_error; + using util::error::DracError, util::error::DracErrorCode; + using util::types::usize, util::types::Err, util::types::Exception, util::types::Result; + using namespace util::cache; + + constexpr opts glaze_opts = { .error_on_unknown_keys = false }; + + fn WriteCallback(void* contents, const usize size, const usize nmemb, weather::String* str) -> usize { + const usize totalSize = size * nmemb; + str->append(static_cast(contents), totalSize); + return totalSize; + } + + fn MakeApiRequest(const weather::String& url) -> Result { + debug_log("Making API request to URL: {}", url); + CURL* curl = curl_easy_init(); + weather::String responseBuffer; + + if (!curl) + return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to initialize cURL")); + + 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); + + const CURLcode res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + if (res != CURLE_OK) + return Err(DracError(DracErrorCode::ApiUnavailable, std::format("cURL error: {}", curl_easy_strerror(res)))); + + weather::OWMResponse owm; + if (const error_ctx errc = read(owm, responseBuffer); errc.ec != error_code::none) + return Err(DracError(DracErrorCode::ParseError, std::format("Failed to parse JSON response: {}", format_error(errc, responseBuffer)))); + + WeatherReport report = { + .temperature = owm.main.temp, + .name = owm.name.empty() ? std::nullopt : util::types::Option(owm.name), + .description = !owm.weather.empty() ? owm.weather[0].description : "", + .timestamp = static_cast(owm.dt), + }; + + return report; + } +} // namespace + +OpenWeatherMapService::OpenWeatherMapService(std::variant location, String apiKey, String units) + : m_location(std::move(location)), m_apiKey(std::move(apiKey)), m_units(std::move(units)) {} + +fn OpenWeatherMapService::getWeatherInfo() const -> Result { + using namespace std::chrono; + + if (Result data = ReadCache("weather")) { + const WeatherReport& dataVal = *data; + + if (const duration cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.timestamp)); cacheAge < 60min) + return dataVal; + + debug_log("Cache expired"); + } else { + using matchit::match, matchit::is, matchit::_; + using enum DracErrorCode; + + DracError err = data.error(); + + match(err.code)( + is | NotFound = [&] { debug_at(err); }, + is | _ = [&] { error_at(err); } + ); + } + + fn handleApiResult = [](const Result& result) -> Result { + if (!result) + return Err(result.error()); + + if (Result<> writeResult = WriteCache("weather", *result); !writeResult) + return Err(writeResult.error()); + + return *result; + }; + + if (std::holds_alternative(m_location)) { + using util::types::i32; + + const auto& city = std::get(m_location); + + char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast(city.length())); + + const String apiUrl = + std::format("https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units={}", escaped, m_apiKey, m_units); + + curl_free(escaped); + + return handleApiResult(MakeApiRequest(apiUrl)); + } + + if (std::holds_alternative(m_location)) { + const auto& [lat, lon] = std::get(m_location); + + const String apiUrl = std::format( + "https://api.openweathermap.org/data/2.5/weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", lat, lon, m_apiKey, m_units + ); + + return handleApiResult(MakeApiRequest(apiUrl)); + } + + return util::types::Err(util::error::DracError(util::error::DracErrorCode::ParseError, "Invalid location type in configuration.")); +} \ No newline at end of file diff --git a/src/Services/Weather/OpenWeatherMapService.hpp b/src/Services/Weather/OpenWeatherMapService.hpp new file mode 100644 index 0000000..bb98596 --- /dev/null +++ b/src/Services/Weather/OpenWeatherMapService.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "IWeatherService.hpp" + +namespace weather { + class OpenWeatherMapService : public IWeatherService { + public: + OpenWeatherMapService(std::variant location, String apiKey, String units); + fn getWeatherInfo() const -> Result override; + + private: + std::variant m_location; + String m_apiKey; + String m_units; + }; +} // namespace weather \ No newline at end of file diff --git a/src/ui/ui.cpp b/src/UI/UI.cpp similarity index 96% rename from src/ui/ui.cpp rename to src/UI/UI.cpp index b1e2be1..428390e 100644 --- a/src/ui/ui.cpp +++ b/src/UI/UI.cpp @@ -1,7 +1,6 @@ -#include "ui.hpp" +#include "UI.hpp" -#include "src/os/os.hpp" -#include "src/util/types.hpp" +#include "Util/Types.hpp" namespace ui { using namespace ftxui; @@ -175,11 +174,11 @@ namespace ui { initialRows.push_back({ .icon = calendarIcon, .label = "Date", .value = *data.date }); if (weather.enabled && data.weather) { - const weather::Output& weatherInfo = *data.weather; + const weather::WeatherReport& weatherInfo = *data.weather; - String weatherValue = weather.showTownName - ? std::format("{}°F in {}", std::lround(weatherInfo.main.temp), weatherInfo.name) - : std::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description); + String weatherValue = weather.showTownName && weatherInfo.name + ? std::format("{}°F in {}", std::lround(weatherInfo.temperature), *weatherInfo.name) + : std::format("{}°F, {}", std::lround(weatherInfo.temperature), weatherInfo.description); initialRows.push_back({ .icon = weatherIcon, .label = "Weather", .value = std::move(weatherValue) }); } else if (weather.enabled && !data.weather.has_value()) diff --git a/src/ui/ui.hpp b/src/UI/UI.hpp similarity index 92% rename from src/ui/ui.hpp rename to src/UI/UI.hpp index 567a872..c0dec58 100644 --- a/src/ui/ui.hpp +++ b/src/UI/UI.hpp @@ -3,9 +3,9 @@ #include // ftxui::Element #include // ftxui::Color -#include "src/config/config.hpp" -#include "src/core/system_data.hpp" -#include "src/util/types.hpp" +#include "Config/Config.hpp" +#include "Core/SystemData.hpp" +#include "Util/Types.hpp" namespace ui { struct Theme { diff --git a/src/util/cache.hpp b/src/Util/Caching.hpp similarity index 96% rename from src/util/cache.hpp rename to src/Util/Caching.hpp index 025e4fc..d0dd91b 100644 --- a/src/util/cache.hpp +++ b/src/Util/Caching.hpp @@ -9,10 +9,10 @@ #include // std::error_code #include // std::decay_t -#include "src/util/defs.hpp" -#include "src/util/error.hpp" -#include "src/util/logging.hpp" -#include "src/util/types.hpp" +#include "Util/Definitions.hpp" +#include "Util/Error.hpp" +#include "Util/Logging.hpp" +#include "Util/Types.hpp" namespace util::cache { namespace fs = std::filesystem; @@ -40,8 +40,7 @@ namespace util::cache { if (!fs::exists(cacheDir, errc)) { if (errc) - return Err(DracError(DracErrorCode::IoError, "Failed to check existence of cache directory: " + errc.message()) - ); + return Err(DracError(DracErrorCode::IoError, "Failed to check existence of cache directory: " + errc.message())); fs::create_directories(cacheDir, errc); @@ -162,8 +161,7 @@ namespace util::cache { if (!ofs) { std::error_code removeEc; fs::remove(tempPath, removeEc); - return Err(DracError(DracErrorCode::IoError, "Failed to write to temporary cache file: " + tempPath.string()) - ); + return Err(DracError(DracErrorCode::IoError, "Failed to write to temporary cache file: " + tempPath.string())); } } diff --git a/src/util/defs.hpp b/src/Util/Definitions.hpp similarity index 100% rename from src/util/defs.hpp rename to src/Util/Definitions.hpp diff --git a/src/util/helpers.hpp b/src/Util/Env.hpp similarity index 81% rename from src/util/helpers.hpp rename to src/Util/Env.hpp index 86da3ee..68a4802 100644 --- a/src/util/helpers.hpp +++ b/src/Util/Env.hpp @@ -1,12 +1,15 @@ #pragma once -#include "src/util/defs.hpp" -#include "src/util/error.hpp" -#include "src/util/types.hpp" +#ifdef _WIN32 + #include // NOLINT(*-deprecated-headers) +#endif + +#include "Util/Definitions.hpp" +#include "Util/Error.hpp" +#include "Util/Types.hpp" namespace util::helpers { - using error::DracError, error::DracErrorCode; - using types::Result, types::String, types::CStr, types::Err; + using types::Result, types::String, types::CStr; /** * @brief Safely retrieves an environment variable. @@ -15,6 +18,9 @@ namespace util::helpers { * or an EnvError if an error occurred. */ [[nodiscard]] inline fn GetEnv(CStr name) -> Result { + using error::DracError, error::DracErrorCode; + using types::Err; + #ifdef _WIN32 using types::i32, types::usize, types::UniquePointer; diff --git a/src/util/error.hpp b/src/Util/Error.hpp similarity index 96% rename from src/util/error.hpp rename to src/Util/Error.hpp index 268352e..d5fc284 100644 --- a/src/util/error.hpp +++ b/src/Util/Error.hpp @@ -1,7 +1,7 @@ #pragma once #include // std::{unexpected, expected} -#include // std::format +#include // matchit::{match, is, or_, _} #include // std::source_location #include // std::error_code @@ -9,11 +9,11 @@ #include // GUID #include // error values #include // winrt::hresult_error +#else + #include // std::format #endif -#include "src/util/types.hpp" - -#include "include/matchit.hpp" +#include "Util/Types.hpp" namespace util { namespace error { @@ -48,15 +48,15 @@ namespace util { struct DracError { // ReSharper disable CppDFANotInitializedField String message; ///< A descriptive error message, potentially including platform details. - DracErrorCode code; ///< The general category of the error. std::source_location location; ///< The source location where the error occurred (file, line, function). + DracErrorCode code; ///< The general category of the error. // ReSharper restore CppDFANotInitializedField DracError(const DracErrorCode errc, String msg, const std::source_location& loc = std::source_location::current()) - : message(std::move(msg)), code(errc), location(loc) {} + : message(std::move(msg)), location(loc), code(errc) {} explicit DracError(const Exception& exc, const std::source_location& loc = std::source_location::current()) - : message(exc.what()), code(DracErrorCode::InternalError), location(loc) {} + : message(exc.what()), location(loc), code(DracErrorCode::InternalError) {} explicit DracError(const std::error_code& errc, const std::source_location& loc = std::source_location::current()) : message(errc.message()), location(loc) { diff --git a/src/util/logging.hpp b/src/Util/Logging.hpp similarity index 99% rename from src/util/logging.hpp rename to src/Util/Logging.hpp index 7763002..e815a04 100644 --- a/src/util/logging.hpp +++ b/src/Util/Logging.hpp @@ -17,9 +17,9 @@ #include // std::source_location #endif -#include "src/util/defs.hpp" -#include "src/util/error.hpp" -#include "src/util/types.hpp" +#include "Util/Definitions.hpp" +#include "Util/Error.hpp" +#include "Util/Types.hpp" namespace util::logging { using types::usize, types::u8, types::i32, types::i64, types::CStr, types::String, types::StringView, types::Array, diff --git a/src/util/types.hpp b/src/Util/Types.hpp similarity index 100% rename from src/util/types.hpp rename to src/Util/Types.hpp diff --git a/src/config/weather.cpp b/src/config/weather.cpp deleted file mode 100644 index 69fe9ed..0000000 --- a/src/config/weather.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#define NOMINMAX -#include "weather.hpp" - -#include // std::chrono::{duration, operator-} -#include // curl_easy_setopt -#include // curl_easy_init, curl_easy_perform, curl_easy_cleanup -#include // std::{expected (Result), unexpected (Err)} -#include // std::format -#include // glz::read_beve -#include // glz::write_beve -#include // glz::{error_ctx, error_code} -#include // glz::opts -#include // glz::format_error -#include // NOLINT(misc-include-cleaner) - glaze/json/read.hpp is needed for glz::read -#include // std::{get, holds_alternative} - -#include "src/util/cache.hpp" -#include "src/util/defs.hpp" -#include "src/util/error.hpp" -#include "src/util/logging.hpp" -#include "src/util/types.hpp" - -#include "config.hpp" - -using weather::Output; - -namespace { - using glz::opts, glz::error_ctx, glz::error_code, glz::read, glz::format_error; - using util::error::DracError, util::error::DracErrorCode; - using util::types::usize, util::types::Err, util::types::Exception; - using weather::Coords; - using namespace util::cache; - - constexpr opts glaze_opts = { .error_on_unknown_keys = false }; - - fn WriteCallback(void* contents, const usize size, const usize nmemb, String* str) -> usize { - const usize totalSize = size * nmemb; - str->append(static_cast(contents), totalSize); - return totalSize; - } - - fn MakeApiRequest(const String& url) -> Result { - debug_log("Making API request to URL: {}", url); - CURL* curl = curl_easy_init(); - String responseBuffer; - - if (!curl) - return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to initialize cURL")); - - 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); - - const CURLcode res = curl_easy_perform(curl); - curl_easy_cleanup(curl); - - if (res != CURLE_OK) - return Err(DracError(DracErrorCode::ApiUnavailable, std::format("cURL error: {}", curl_easy_strerror(res)))); - - Output output; - - if (const error_ctx errc = read(output, responseBuffer); errc.ec != error_code::none) - return Err(DracError( - DracErrorCode::ParseError, std::format("Failed to parse JSON response: {}", format_error(errc, responseBuffer)) - )); - - return output; - } -} // namespace - -fn Weather::getWeatherInfo() const -> Result { - using namespace std::chrono; - using util::types::i32; - - if (Result data = ReadCache("weather")) { - const Output& dataVal = *data; - - if (const duration cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt)); - cacheAge < 60min) // NOLINT(misc-include-cleaner) - inherited from - return dataVal; - - debug_log("Cache expired"); - } else if (data.error().code == DracErrorCode::NotFound) { - debug_at(data.error()); - } else - error_at(data.error()); - - fn handleApiResult = [](const Result& result) -> Result { - if (!result) - return Err(result.error()); - - if (Result writeResult = WriteCache("weather", *result); !writeResult) - return Err(writeResult.error()); - - return *result; - }; - - 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 = - std::format("https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units={}", escaped, apiKey, units); - - curl_free(escaped); - - return handleApiResult(MakeApiRequest(apiUrl)); - } - - if (std::holds_alternative(location)) { - const auto& [lat, lon] = std::get(location); - - const String apiUrl = std::format( - "https://api.openweathermap.org/data/2.5/weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", lat, lon, apiKey, units - ); - - return handleApiResult(MakeApiRequest(apiUrl)); - } - - return Err(DracError(DracErrorCode::ParseError, "Invalid location type in configuration.")); -} diff --git a/src/config/weather.hpp b/src/config/weather.hpp deleted file mode 100644 index 747173d..0000000 --- a/src/config/weather.hpp +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include // object -#include // Object - -#include "src/util/types.hpp" - -namespace weather { - using glz::detail::Object, glz::object; - using util::types::String, util::types::Vec, util::types::f64, util::types::usize; - - // NOLINTBEGIN(readability-identifier-naming) - Needs to specifically use `glaze` - /** - * @struct Condition - * @brief Represents weather conditions. - */ - struct Condition { - String description; ///< Weather condition description (e.g., "clear sky", "light rain"). - - /** - * @brief Glaze serialization and deserialization for Condition. - */ - struct [[maybe_unused]] glaze { - using T = Condition; - - static constexpr Object value = object("description", &T::description); - }; - }; - - /** - * @struct Main - * @brief Represents the main weather data. - */ - struct Main { - f64 temp; ///< Temperature in degrees (C/F, depending on config). - - /** - * @brief Glaze serialization and deserialization for Main. - */ - struct [[maybe_unused]] glaze { - using T = Main; - - static constexpr Object value = object("temp", &T::temp); - }; - }; - - /** - * @struct Coords - * @brief Represents geographical coordinates. - */ - struct Coords { - double lat; ///< Latitude coordinate. - double lon; ///< Longitude coordinate. - }; - - /** - * @struct Output - * @brief Represents the output of the weather API. - * - * Contains main weather data, location name, and weather conditions. - */ - struct Output { - Main main; ///< Main weather data (temperature, etc.). - String name; ///< Location name (e.g., city name). - Vec weather; ///< List of weather conditions (e.g., clear, rain). - usize dt; ///< Timestamp of the weather data (in seconds since epoch). - - /** - * @brief Glaze serialization and deserialization for WeatherOutput. - */ - struct [[maybe_unused]] glaze { - using T = Output; - - static constexpr Object value = object("main", &T::main, "name", &T::name, "weather", &T::weather, "dt", &T::dt); - }; - }; - // NOLINTEND(readability-identifier-naming) -} // namespace weather diff --git a/src/main.cpp b/src/main.cpp index a606213..6100c01 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,23 +1,21 @@ +#include // argparse::ArgumentParser #include // EXIT_FAILURE, EXIT_SUCCESS #include // ftxui::{Element, hbox, vbox, text, separator, filler, etc.} #include // ftxui::{Render} #include // ftxui::{Screen, Dimension::Full} -#include "src/ui/ui.hpp" - #ifdef __cpp_lib_print #include // std::print #else #include // std::cout #endif -#include "src/config/config.hpp" -#include "src/core/system_data.hpp" -#include "src/util/defs.hpp" -#include "src/util/logging.hpp" -#include "src/util/types.hpp" - -#include "include/argparse.hpp" +#include "Config/Config.hpp" +#include "Core/SystemData.hpp" +#include "UI/UI.hpp" +#include "Util/Definitions.hpp" +#include "Util/Logging.hpp" +#include "Util/Types.hpp" using util::types::i32, util::types::Exception; @@ -27,45 +25,49 @@ fn main(const i32 argc, char* argv[]) -> i32 { winrt::init_apartment(); #endif - argparse::ArgumentParser parser("draconis", "0.1.0"); - - parser - .add_argument("--log-level") - .help("Set the log level") - .default_value("info") - .choices("debug", "info", "warn", "error"); - - parser - .add_argument("-V", "--verbose") - .help("Enable verbose logging. Overrides --log-level.") - .flag(); - - if (Result result = parser.parse_args(argc, argv); !result) { - error_at(result.error()); - return EXIT_FAILURE; - } - - bool verbose = parser.get("-V").value_or(false) || parser.get("--verbose").value_or(false); - Result logLevelStr = verbose ? "debug" : parser.get("--log-level"); - { - using matchit::match, matchit::is, matchit::_; - using util::logging::LogLevel; - using enum util::logging::LogLevel; + using argparse::ArgumentParser; - LogLevel minLevel = match(logLevelStr)( - is | "debug" = Debug, - is | "info" = Info, - is | "warn" = Warn, - is | "error" = Error, + ArgumentParser parser("draconis", "0.1.0"); + + parser + .add_argument("--log-level") + .help("Set the log level") + .default_value("info") + .choices("debug", "info", "warn", "error"); + + parser + .add_argument("-V", "--verbose") + .help("Enable verbose logging. Overrides --log-level.") + .flag(); + + if (Result result = parser.parse_args(argc, argv); !result) { + error_at(result.error()); + return EXIT_FAILURE; + } + + bool verbose = parser.get("-V").value_or(false) || parser.get("--verbose").value_or(false); + Result logLevelStr = verbose ? "debug" : parser.get("--log-level"); + + { + using matchit::match, matchit::is, matchit::_; + using util::logging::LogLevel; + using enum util::logging::LogLevel; + + LogLevel minLevel = match(logLevelStr)( + is | "debug" = Debug, + is | "info" = Info, + is | "warn" = Warn, + is | "error" = Error, #ifndef NDEBUG - is | _ = Debug + is | _ = Debug #else - is | _ = Info + is | _ = Info #endif - ); + ); - SetRuntimeLogLevel(minLevel); + SetRuntimeLogLevel(minLevel); + } } const Config& config = Config::getInstance(); diff --git a/src/wrappers/dbus.hpp b/src/wrappers/DBus.hpp similarity index 100% rename from src/wrappers/dbus.hpp rename to src/wrappers/DBus.hpp diff --git a/src/wrappers/wayland.hpp b/src/wrappers/Wayland.hpp similarity index 100% rename from src/wrappers/wayland.hpp rename to src/wrappers/Wayland.hpp diff --git a/src/wrappers/xcb.hpp b/src/wrappers/XCB.hpp similarity index 100% rename from src/wrappers/xcb.hpp rename to src/wrappers/XCB.hpp