lots of new stuff waow

This commit is contained in:
Mars 2025-05-11 00:38:34 -04:00
parent bb4ccb5d42
commit 867bab1050
38 changed files with 704 additions and 452 deletions

View file

@ -73,9 +73,9 @@
#include <vector> #include <vector>
#endif #endif
#include "src/util/defs.hpp" #include "Util/Definitions.hpp"
#include "src/util/error.hpp" #include "Util/Error.hpp"
#include "src/util/types.hpp" #include "Util/Types.hpp"
#ifndef ARGPARSE_CUSTOM_STRTOF #ifndef ARGPARSE_CUSTOM_STRTOF
#define ARGPARSE_CUSTOM_STRTOF strtof #define ARGPARSE_CUSTOM_STRTOF strtof

View file

@ -17,7 +17,7 @@
#include <utility> #include <utility>
#include <variant> #include <variant>
#include "src/util/defs.hpp" #include "Util/Definitions.hpp"
// NOLINTBEGIN(readability-identifier-*, cppcoreguidelines-special-member-functions) // NOLINTBEGIN(readability-identifier-*, cppcoreguidelines-special-member-functions)
namespace matchit { namespace matchit {

View file

@ -4,8 +4,8 @@
project( project(
'draconis++', 'draconis++',
'cpp', 'cpp',
version: '0.1.0', version : '0.1.0',
default_options: [ default_options : [
'default_library=static', 'default_library=static',
'buildtype=debugoptimized', 'buildtype=debugoptimized',
'b_vscrt=mt', 'b_vscrt=mt',
@ -35,13 +35,13 @@ common_warning_flags = [
] ]
common_cpp_flags = { common_cpp_flags = {
'common': [ 'common' : [
'-fno-strict-enums', '-fno-strict-enums',
'-fvisibility=hidden', '-fvisibility=hidden',
'-fvisibility-inlines-hidden', '-fvisibility-inlines-hidden',
'-std=c++26', '-std=c++26',
], ],
'msvc': [ 'msvc' : [
'-DNOMINMAX', '/MT', '-DNOMINMAX', '/MT',
'/Zc:__cplusplus', '/Zc:__cplusplus',
'/Zc:preprocessor', '/Zc:preprocessor',
@ -49,20 +49,20 @@ common_cpp_flags = {
'/external:anglebrackets', '/external:anglebrackets',
'/std:c++latest', '/std:c++latest',
], ],
'unix_extra': '-march=native', 'unix_extra' : '-march=native',
'windows_extra': '-DCURL_STATICLIB', 'windows_extra' : '-DCURL_STATICLIB',
} }
# Configure Objective-C++ for macOS # Configure Objective-C++ for macOS
if host_system == 'darwin' if host_system == 'darwin'
add_languages('objcpp', native: false) add_languages('objcpp', native : false)
objcpp = meson.get_compiler('objcpp') objcpp = meson.get_compiler('objcpp')
objcpp_flags = common_warning_flags + [ objcpp_flags = common_warning_flags + [
'-std=c++26', '-std=c++26',
'-fvisibility=hidden', '-fvisibility=hidden',
'-fvisibility-inlines-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 endif
# Apply C++ compiler arguments # Apply C++ compiler arguments
@ -80,29 +80,36 @@ else
endif endif
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 # # Files #
# ------- # # ------- #
base_sources = files( base_sources = files(
'src/config/config.cpp', 'src/Config/Config.cpp',
'src/config/weather.cpp', 'src/Core/SystemData.cpp',
'src/core/package.cpp', 'src/Services/Weather/OpenMeteoService.cpp',
'src/core/system_data.cpp', 'src/Services/Weather/OpenWeatherMapService.cpp',
'src/Services/PackageCounting.cpp',
'src/UI/UI.cpp',
'src/main.cpp', 'src/main.cpp',
'src/ui/ui.cpp',
) )
platform_sources = { platform_sources = {
'darwin': ['src/os/macos.cpp', 'src/os/macos/bridge.mm'], 'darwin' : ['src/OS/macOS.cpp', 'src/OS/macOS/bridge.mm'],
'dragonfly': ['src/os/bsd.cpp'], 'dragonfly' : ['src/OS/bsd.cpp'],
'freebsd': ['src/os/bsd.cpp'], 'freebsd' : ['src/OS/bsd.cpp'],
'haiku': ['src/os/haiku.cpp'], 'haiku' : ['src/OS/haiku.cpp'],
'linux': ['src/os/linux.cpp'], 'linux' : ['src/OS/linux.cpp'],
'netbsd': ['src/os/bsd.cpp'], 'netbsd' : ['src/OS/bsd.cpp'],
'serenity': ['src/os/serenity.cpp'], 'serenity' : ['src/OS/serenity.cpp'],
'windows': ['src/os/windows.cpp'], 'windows' : ['src/OS/Windows.cpp'],
} }
sources = base_sources + files(platform_sources.get(host_system, [])) sources = base_sources + files(platform_sources.get(host_system, []))
@ -111,9 +118,9 @@ sources = base_sources + files(platform_sources.get(host_system, []))
# Dependencies Config # # Dependencies Config #
# --------------------- # # --------------------- #
common_deps = [ common_deps = [
dependency('libcurl', include_type: 'system', static: true), dependency('libcurl', include_type : 'system', static : true),
dependency('tomlplusplus', include_type: 'system', static: true), dependency('tomlplusplus', include_type : 'system', static : true),
dependency('openssl', include_type: 'system', static: true, required: false), dependency('openssl', include_type : 'system', static : true, required : false),
] ]
# Platform-specific dependencies # Platform-specific dependencies
@ -124,8 +131,8 @@ if host_system == 'darwin'
dependency('SQLiteCpp'), dependency('SQLiteCpp'),
dependency( dependency(
'appleframeworks', 'appleframeworks',
modules: ['foundation', 'mediaplayer', 'systemconfiguration'], modules : ['foundation', 'mediaplayer', 'systemconfiguration'],
static: true, static : true,
), ),
dependency('iconv'), dependency('iconv'),
] ]
@ -136,11 +143,11 @@ elif host_system == 'windows'
] ]
elif host_system != 'serenity' and host_system != 'haiku' elif host_system != 'serenity' and host_system != 'haiku'
# Make dbus, x11, and wayland dependencies optional # Make dbus, x11, and wayland dependencies optional
dbus_dep = dependency('dbus-1', required: false) dbus_dep = dependency('dbus-1', required : false)
xcb_dep = dependency('xcb', required: false) xcb_dep = dependency('xcb', required : false)
xau_dep = dependency('xau', required: false) xau_dep = dependency('xau', required : false)
xdmcp_dep = dependency('xdmcp', required: false) xdmcp_dep = dependency('xdmcp', required : false)
wayland_dep = dependency('wayland-client', required: false) wayland_dep = dependency('wayland-client', required : false)
platform_deps += [ platform_deps += [
dependency('SQLiteCpp'), dependency('SQLiteCpp'),
@ -149,15 +156,15 @@ elif host_system != 'serenity' and host_system != 'haiku'
if dbus_dep.found() if dbus_dep.found()
platform_deps += dbus_dep platform_deps += dbus_dep
add_project_arguments('-DHAVE_DBUS', language: 'cpp') add_project_arguments('-DHAVE_DBUS', language : 'cpp')
endif endif
if xcb_dep.found() and xau_dep.found() and xdmcp_dep.found() if xcb_dep.found() and xau_dep.found() and xdmcp_dep.found()
platform_deps += [xcb_dep, xau_dep, xdmcp_dep] platform_deps += [xcb_dep, xau_dep, xdmcp_dep]
add_project_arguments('-DHAVE_XCB', language: 'cpp') add_project_arguments('-DHAVE_XCB', language : 'cpp')
endif endif
if wayland_dep.found() if wayland_dep.found()
platform_deps += wayland_dep platform_deps += wayland_dep
add_project_arguments('-DHAVE_WAYLAND', language: 'cpp') add_project_arguments('-DHAVE_WAYLAND', language : 'cpp')
endif endif
endif endif
@ -165,28 +172,28 @@ endif
ftxui_components = ['ftxui::screen', 'ftxui::dom', 'ftxui::component'] ftxui_components = ['ftxui::screen', 'ftxui::dom', 'ftxui::component']
ftxui_dep = dependency( ftxui_dep = dependency(
'ftxui', 'ftxui',
modules: ftxui_components, modules : ftxui_components,
include_type: 'system', include_type : 'system',
static: true, static : true,
required: false, required : false,
) )
if not ftxui_dep.found() if not ftxui_dep.found()
ftxui_dep = declare_dependency( ftxui_dep = declare_dependency(
dependencies: [ dependencies : [
dependency('ftxui-dom', fallback: ['ftxui', 'dom_dep']), dependency('ftxui-dom', fallback : ['ftxui', 'dom_dep']),
dependency('ftxui-screen', fallback: ['ftxui', 'screen_dep']), dependency('ftxui-screen', fallback : ['ftxui', 'screen_dep']),
dependency('ftxui-component', fallback: ['ftxui', 'component_dep']), dependency('ftxui-component', fallback : ['ftxui', 'component_dep']),
], ],
) )
endif endif
glaze_dep = dependency('glaze', include_type: 'system', required: false) glaze_dep = dependency('glaze', include_type : 'system', required : false)
if not glaze_dep.found() if not glaze_dep.found()
cmake = import('cmake') cmake = import('cmake')
glaze_proj = cmake.subproject('glaze') 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 endif
# Combine all dependencies # Combine all dependencies
@ -212,8 +219,9 @@ endif
executable( executable(
'draconis++', 'draconis++',
sources, sources,
objc_args: objc_args, include_directories : [project_internal_includes, project_public_includes],
link_args: link_args, objc_args : objc_args,
dependencies: deps, link_args : link_args,
install: true, dependencies : deps,
install : true,
) )

View file

@ -1,4 +1,4 @@
#include "config.hpp" #include "Config.hpp"
#include <filesystem> // std::filesystem::{path, operator/, exists, create_directories} #include <filesystem> // std::filesystem::{path, operator/, exists, create_directories}
#include <format> // std::{format, format_error} #include <format> // std::{format, format_error}
@ -8,15 +8,10 @@
#include <toml++/impl/parser.hpp> // toml::{parse_file, parse_result} #include <toml++/impl/parser.hpp> // toml::{parse_file, parse_result}
#include <toml++/impl/table.hpp> // toml::table #include <toml++/impl/table.hpp> // toml::table
#ifndef _WIN32 #include "Util/Definitions.hpp"
#include <pwd.h> // passwd, getpwuid #include "Util/Env.hpp"
#include <unistd.h> // getuid #include "Util/Logging.hpp"
#endif #include "Util/Types.hpp"
#include "src/util/defs.hpp"
#include "src/util/helpers.hpp"
#include "src/util/logging.hpp"
#include "src/util/types.hpp"
namespace fs = std::filesystem; namespace fs = std::filesystem;

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <memory> // std::{make_unique, unique_ptr}
#include <toml++/impl/node.hpp> // toml::node #include <toml++/impl/node.hpp> // toml::node
#include <toml++/impl/node_view.hpp> // toml::node_view #include <toml++/impl/node_view.hpp> // toml::node_view
#include <toml++/impl/table.hpp> // toml::table #include <toml++/impl/table.hpp> // toml::table
@ -11,15 +12,16 @@
#include <pwd.h> // getpwuid, passwd #include <pwd.h> // getpwuid, passwd
#include <unistd.h> // getuid #include <unistd.h> // getuid
#include "src/util/helpers.hpp" #include "Util/Env.hpp"
#endif #endif
#include "src/util/defs.hpp" #include "../Services/Weather/OpenMeteoService.hpp"
#include "src/util/error.hpp" #include "../Services/Weather/OpenWeatherMapService.hpp"
#include "src/util/logging.hpp" #include "Services/Weather.hpp"
#include "src/util/types.hpp" #include "Util/Definitions.hpp"
#include "Util/Error.hpp"
#include "weather.hpp" #include "Util/Logging.hpp"
#include "Util/Types.hpp"
using util::error::DracError; using util::error::DracError;
using util::types::CStr, util::types::String, util::types::Array, util::types::Option, util::types::Result; 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 apiKey; ///< API key for the weather service.
String units; ///< Units for temperature, either "metric" or "imperial". String units; ///< Units for temperature, either "metric" or "imperial".
bool enabled = false; ///< Flag to enable or disable the Weather feature. bool enabled = false; ///< Flag to enable or disable the Weather feature.
bool showTownName = false; ///< Flag to show the town name in the output. bool showTownName = false; ///< Flag to show the town name in the output.
std::unique_ptr<weather::IWeatherService> service = nullptr; ///< Pointer to the weather service.
/** /**
* @brief Parses a TOML table to create a Weather instance. * @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. * @return A Weather instance with the parsed values, or defaults otherwise.
*/ */
static fn fromToml(const toml::table& tbl) -> Weather { static fn fromToml(const toml::table& tbl) -> Weather {
Weather weather; Weather weather;
const Option<String> apiKey = tbl["api_key"].value<String>(); const Option<String> apiKey = tbl["api_key"].value<String>();
weather.enabled = tbl["enabled"].value_or<bool>(false) && apiKey;
weather.enabled = tbl["enabled"].value_or<bool>(false) && apiKey;
if (!weather.enabled) if (!weather.enabled)
return weather; return weather;
@ -125,6 +126,9 @@ struct Weather {
weather.showTownName = tbl["show_town_name"].value_or(false); weather.showTownName = tbl["show_town_name"].value_or(false);
weather.units = tbl["units"].value_or("metric"); 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<const toml::node> location = tbl["location"]) { if (const toml::node_view<const toml::node> location = tbl["location"]) {
if (location.is_string()) if (location.is_string())
weather.location = *location.value<String>(); weather.location = *location.value<String>();
@ -137,20 +141,27 @@ struct Weather {
error_log("Invalid location format in config."); error_log("Invalid location format in config.");
weather.enabled = false; 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::Coords>(weather.location)) {
const auto& coords = std::get<weather::Coords>(weather.location);
weather.service = std::make_unique<weather::OpenMeteoService>(coords.lat, coords.lon, weather.units);
} else {
error_log("OpenMeteo requires coordinates for location.");
weather.enabled = false;
}
} else {
weather.service = std::make_unique<weather::OpenWeatherMapService>(weather.location, weather.apiKey, weather.units);
}
} }
return weather; 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<weather::Output>;
}; };
/** /**

View file

@ -1,19 +1,18 @@
#include "system_data.hpp" #include "SystemData.hpp"
#include <chrono> // std::chrono::system_clock #include <chrono> // std::chrono::system_clock
#include <ctime> // localtime_r/s, strftime, time_t, tm #include <ctime> // localtime_r/s, strftime, time_t, tm
#include <format> // std::format #include <format> // std::format
#include <future> // std::{async, launch} #include <future> // std::{async, launch}
#include <matchit.hpp> // matchit::{match, is, in, _}
#include "src/config/config.hpp" #include "Config/Config.hpp"
#include "src/config/weather.hpp" #include "OS/OperatingSystem.hpp"
#include "src/core/package.hpp" #include "Services/PackageCounting.hpp"
#include "src/os/os.hpp" #include "Services/Weather.hpp"
#include "src/util/defs.hpp" #include "Util/Definitions.hpp"
#include "src/util/error.hpp" #include "Util/Error.hpp"
#include "src/util/types.hpp" #include "Util/Types.hpp"
#include "include/matchit.hpp"
using util::error::DracError, util::error::DracErrorCode; using util::error::DracError, util::error::DracErrorCode;
@ -73,18 +72,20 @@ namespace os {
using package::GetTotalCount; using package::GetTotalCount;
using util::types::Future, util::types::Err; using util::types::Future, util::types::Err;
Future<Result<String>> hostFut = std::async(async, GetHost); Future<Result<String>> hostFut = std::async(async, GetHost);
Future<Result<String>> kernelFut = std::async(async, GetKernelVersion); Future<Result<String>> kernelFut = std::async(async, GetKernelVersion);
Future<Result<String>> osFut = std::async(async, GetOSVersion); Future<Result<String>> osFut = std::async(async, GetOSVersion);
Future<Result<u64>> memFut = std::async(async, GetMemInfo); Future<Result<u64>> memFut = std::async(async, GetMemInfo);
Future<Result<String>> deFut = std::async(async, GetDesktopEnvironment); Future<Result<String>> deFut = std::async(async, GetDesktopEnvironment);
Future<Result<String>> wmFut = std::async(async, GetWindowManager); Future<Result<String>> wmFut = std::async(async, GetWindowManager);
Future<Result<DiskSpace>> diskFut = std::async(async, GetDiskUsage); Future<Result<DiskSpace>> diskFut = std::async(async, GetDiskUsage);
Future<Result<String>> shellFut = std::async(async, GetShell); Future<Result<String>> shellFut = std::async(async, GetShell);
Future<Result<u64>> pkgFut = std::async(async, GetTotalCount); Future<Result<u64>> pkgFut = std::async(async, GetTotalCount);
Future<Result<MediaInfo>> npFut = std::async(config.nowPlaying.enabled ? async : deferred, GetNowPlaying); Future<Result<MediaInfo>> npFut = std::async(config.nowPlaying.enabled ? async : deferred, GetNowPlaying);
Future<Result<weather::Output>> wthrFut = std::async(config.weather.enabled ? async : deferred, [&config] { Future<Result<weather::WeatherReport>> wthrFut = std::async(config.weather.enabled ? async : deferred, [&config]() -> Result<weather::WeatherReport> {
return config.weather.getWeatherInfo(); 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->diskUsage = diskFut.get();
this->shell = shellFut.get(); this->shell = shellFut.get();
this->packageCount = pkgFut.get(); this->packageCount = pkgFut.get();
this->weather = config.weather.enabled ? wthrFut.get() : Err(DracError(ApiUnavailable, "Weather 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(ApiUnavailable, "Now Playing API disabled")); this->nowPlaying = config.nowPlaying.enabled ? npFut.get() : Err(DracError(util::error::DracErrorCode::ApiUnavailable, "Now Playing API disabled"));
} }
} }
} // namespace os } // namespace os

View file

@ -2,10 +2,10 @@
#include <format> // std::{formatter, format_to} #include <format> // std::{formatter, format_to}
#include "src/config/config.hpp" #include "Services/Weather.hpp"
#include "src/config/weather.hpp" #include "Util/Definitions.hpp"
#include "src/util/defs.hpp" #include "Util/Error.hpp"
#include "src/util/types.hpp" #include "Util/Types.hpp"
struct Config; struct Config;
@ -26,7 +26,8 @@ struct BytesToGiB {
* @brief Constructor for BytesToGiB. * @brief Constructor for BytesToGiB.
* @param value The byte value to be converted. * @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 /// @brief Conversion factor from bytes to GiB
@ -72,18 +73,18 @@ namespace os {
* in order to display it at all at once during runtime. * in order to display it at all at once during runtime.
*/ */
struct SystemData { struct SystemData {
Result<String> date; ///< Current date (e.g., "April 26th"). Result<String> date; ///< Current date (e.g., "April 26th").
Result<String> host; ///< Host/product family (e.g., "MacBook Air"). Result<String> host; ///< Host/product family (e.g., "MacBook Air").
Result<String> kernelVersion; ///< OS kernel version (e.g., "6.14.4"). Result<String> kernelVersion; ///< OS kernel version (e.g., "6.14.4").
Result<String> osVersion; ///< OS pretty name (e.g., "Ubuntu 24.04.2 LTS"). Result<String> osVersion; ///< OS pretty name (e.g., "Ubuntu 24.04.2 LTS").
Result<u64> memInfo; ///< Total physical RAM in bytes. Result<u64> memInfo; ///< Total physical RAM in bytes.
Result<String> desktopEnv; ///< Desktop environment (e.g., "KDE"). Result<String> desktopEnv; ///< Desktop environment (e.g., "KDE").
Result<String> windowMgr; ///< Window manager (e.g., "KWin"). Result<String> windowMgr; ///< Window manager (e.g., "KWin").
Result<DiskSpace> diskUsage; ///< Used/Total disk space for root filesystem. Result<DiskSpace> diskUsage; ///< Used/Total disk space for root filesystem.
Result<String> shell; ///< Name of the current user shell (e.g., "zsh"). Result<String> shell; ///< Name of the current user shell (e.g., "zsh").
Result<u64> packageCount; ///< Total number of packages installed. Result<u64> packageCount; ///< Total number of packages installed.
Result<MediaInfo> nowPlaying; ///< Result of fetching media info. Result<MediaInfo> nowPlaying; ///< Result of fetching media info.
Result<weather::Output> weather; ///< Result of fetching weather info. Result<weather::WeatherReport> weather; ///< Result of fetching weather info.
/** /**
* @brief Constructs a SystemData object and initializes its members. * @brief Constructs a SystemData object and initializes its members.

View file

@ -1,8 +1,8 @@
#pragma once #pragma once
#include "src/util/defs.hpp" #include "Util/Definitions.hpp"
#include "src/util/error.hpp" #include "Util/Error.hpp"
#include "src/util/types.hpp" #include "Util/Types.hpp"
/** /**
* @namespace os * @namespace os

View file

@ -11,18 +11,15 @@
#include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Management.Deployment.h> #include <winrt/Windows.Management.Deployment.h>
#include <winrt/Windows.Media.Control.h> #include <winrt/Windows.Media.Control.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.System.Profile.h> #include <winrt/Windows.System.Profile.h>
#include <winrt/base.h>
#include <winrt/impl/Windows.Media.Control.2.h>
#include "src/core/package.hpp" #include "Services/PackageCounting.hpp"
#include "src/util/error.hpp" #include "Util/Env.hpp"
#include "src/util/helpers.hpp" #include "Util/Error.hpp"
#include "src/util/logging.hpp" #include "Util/Logging.hpp"
#include "src/util/types.hpp" #include "Util/Types.hpp"
#include "os.hpp" #include "OperatingSystem.hpp"
// clang-format on // clang-format on
using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW); using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW);
@ -65,8 +62,7 @@ namespace {
String value((type == REG_SZ || type == REG_EXPAND_SZ) ? dataSize - 1 : dataSize, '\0'); String value((type == REG_SZ || type == REG_EXPAND_SZ) ? dataSize - 1 : dataSize, '\0');
// NOLINTNEXTLINE(*-pro-type-reinterpret-cast) - required here // NOLINTNEXTLINE(*-pro-type-reinterpret-cast) - required here
if (RegQueryValueExA(key, valueName.c_str(), nullptr, nullptr, reinterpret_cast<LPBYTE>(value.data()), &dataSize) != if (RegQueryValueExA(key, valueName.c_str(), nullptr, nullptr, reinterpret_cast<LPBYTE>(value.data()), &dataSize) != ERROR_SUCCESS) {
ERROR_SUCCESS) {
RegCloseKey(key); RegCloseKey(key);
return ""; return "";
} }
@ -175,10 +171,7 @@ namespace os {
if (GlobalMemoryStatusEx(&memInfo)) if (GlobalMemoryStatusEx(&memInfo))
return memInfo.ullTotalPhys; return memInfo.ullTotalPhys;
DWORD lastError = GetLastError(); return Err(DracError(DracErrorCode::PlatformSpecific, std::format("GlobalMemoryStatusEx failed with error code {}", GetLastError())));
return Err(DracError(
DracErrorCode::PlatformSpecific, std::format("GlobalMemoryStatusEx failed with error code {}", lastError)
));
} }
fn GetNowPlaying() -> Result<MediaInfo> { fn GetNowPlaying() -> Result<MediaInfo> {
@ -216,18 +209,15 @@ namespace os {
if (const Option<u64> buildNumberOpt = GetBuildNumber()) { if (const Option<u64> buildNumberOpt = GetBuildNumber()) {
if (const u64 buildNumber = *buildNumberOpt; buildNumber >= 22000) { if (const u64 buildNumber = *buildNumberOpt; buildNumber >= 22000) {
if (const size_t pos = productName.find("Windows 10"); pos != String::npos) { if (const size_t pos = productName.find("Windows 10"); pos != String::npos) {
const bool startBoundary = (pos == 0 || !isalnum(static_cast<unsigned char>(productName[pos - 1]))); const bool startBoundary = (pos == 0 || !isalnum(static_cast<u8>(productName[pos - 1])));
const bool endBoundary = const bool endBoundary = (pos + 10 == productName.length() || !isalnum(static_cast<u8>(productName[pos + 10])));
(pos + 10 == productName.length() || !isalnum(static_cast<unsigned char>(productName[pos + 10])));
if (startBoundary && endBoundary) { if (startBoundary && endBoundary)
productName.replace(pos, 10, "Windows 11"); productName.replace(pos, 10, "Windows 11");
}
} }
} }
} else { } else
debug_log("Warning: Could not get build number via WinRT; Win11 detection might be inaccurate."); debug_log("Warning: Could not get build number via WinRT; Win11 detection might be inaccurate.");
}
return displayVersion.empty() ? productName : productName + " " + displayVersion; return displayVersion.empty() ? productName : productName + " " + displayVersion;
} catch (const std::exception& e) { return Err(DracError(e)); } } catch (const std::exception& e) { return Err(DracError(e)); }
@ -245,9 +235,7 @@ namespace os {
osInfo.dwOSVersionInfoSize = sizeof(osInfo); osInfo.dwOSVersionInfoSize = sizeof(osInfo);
if (rtlGetVersion(&osInfo) == 0) if (rtlGetVersion(&osInfo) == 0)
return std::format( return std::format("{}.{}.{}.{}", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwPlatformId);
"{}.{}.{}.{}", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwPlatformId
);
} }
} }

View file

@ -1,4 +1,4 @@
#include "package.hpp" #include "PackageCounting.hpp"
#if !defined(__serenity__) && !defined(_WIN32) #if !defined(__serenity__) && !defined(_WIN32)
#include <SQLiteCpp/Database.h> // SQLite::{Database, OPEN_READONLY} #include <SQLiteCpp/Database.h> // SQLite::{Database, OPEN_READONLY}
@ -14,15 +14,14 @@
#include <filesystem> // std::filesystem #include <filesystem> // std::filesystem
#include <format> // std::format #include <format> // std::format
#include <future> // std::{async, future, launch} #include <future> // std::{async, future, launch}
#include <matchit.hpp> // matchit::{match, is, or_, _}
#include <system_error> // std::{errc, error_code} #include <system_error> // std::{errc, error_code}
#include "src/util/cache.hpp" #include "Util/Caching.hpp"
#include "src/util/error.hpp" #include "Util/Env.hpp"
#include "src/util/helpers.hpp" #include "Util/Error.hpp"
#include "src/util/logging.hpp" #include "Util/Logging.hpp"
#include "src/util/types.hpp" #include "Util/Types.hpp"
#include "include/matchit.hpp"
namespace { namespace {
namespace fs = std::filesystem; namespace fs = std::filesystem;
@ -454,12 +453,11 @@ namespace package {
totalCount += *result; totalCount += *result;
oneSucceeded = true; oneSucceeded = true;
debug_log("Added {} packages. Current total: {}", *result, totalCount); debug_log("Added {} packages. Current total: {}", *result, totalCount);
} else { } else
match(result.error().code)( match(result.error().code)(
is | or_(NotFound, ApiUnavailable, NotSupported) = [&] -> void { debug_at(result.error()); }, is | or_(NotFound, ApiUnavailable, NotSupported) = [&] -> void { debug_at(result.error()); },
is | _ = [&] -> void { error_at(result.error()); } is | _ = [&] -> void { error_at(result.error()); }
); );
}
} catch (const Exception& exc) { } catch (const Exception& exc) {
error_log("Caught exception while getting package count future: {}", exc.what()); error_log("Caught exception while getting package count future: {}", exc.what());
} catch (...) { error_log("Caught unknown exception while getting package count future."); } } catch (...) { error_log("Caught unknown exception while getting package count future."); }

View file

@ -4,9 +4,9 @@
#include <glaze/core/common.hpp> // glz::object #include <glaze/core/common.hpp> // glz::object
#include <glaze/core/meta.hpp> // glz::detail::Object #include <glaze/core/meta.hpp> // glz::detail::Object
#include "src/util/defs.hpp" #include "Util/Definitions.hpp"
#include "src/util/error.hpp" #include "Util/Error.hpp"
#include "src/util/types.hpp" #include "Util/Types.hpp"
namespace package { namespace package {
namespace fs = std::filesystem; namespace fs = std::filesystem;
@ -22,7 +22,8 @@ namespace package {
i64 timestampEpochSeconds {}; i64 timestampEpochSeconds {};
PkgCountCacheData() = default; PkgCountCacheData() = default;
PkgCountCacheData(u64 count, i64 timestampEpochSeconds) : count(count), timestampEpochSeconds(timestampEpochSeconds) {} PkgCountCacheData(u64 count, i64 timestampEpochSeconds)
: count(count), timestampEpochSeconds(timestampEpochSeconds) {}
// NOLINTBEGIN(readability-identifier-naming) // NOLINTBEGIN(readability-identifier-naming)
struct [[maybe_unused]] glaze { struct [[maybe_unused]] glaze {
@ -132,8 +133,7 @@ namespace package {
fn GetSerenityCount() -> Result<u64>; fn GetSerenityCount() -> Result<u64>;
#endif #endif
// Common (potentially cross-platform) #if defined(__linux__) || defined(__APPLE__)
#ifndef _WIN32
fn CountNix() -> Result<u64>; fn CountNix() -> Result<u64>;
#endif #endif
fn CountCargo() -> Result<u64>; fn CountCargo() -> Result<u64>;

49
src/Services/Weather.hpp Normal file
View file

@ -0,0 +1,49 @@
#pragma once
#include <glaze/core/common.hpp> // object
#include <glaze/core/meta.hpp> // 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<String> 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

View file

@ -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<WeatherReport> = 0;
protected:
IWeatherService() = default;
};
} // namespace weather

View file

@ -0,0 +1,156 @@
#define NOMINMAX
#include "OpenMeteoService.hpp"
#include <chrono>
#include <curl/curl.h>
#include <curl/easy.h>
#include <format>
#include <glaze/json/read.hpp>
#include <sstream>
#include <string>
#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::OpenMeteoResponse> : weather::OpenMeteoGlaze {};
template <>
struct glz::meta<weather::OpenMeteoResponse::CurrentWeather> : 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<char*>(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<util::types::usize>(_mkgmtime(&time));
#else
return static_cast<util::types::usize>(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<WeatherReport> {
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<WeatherReport> data = ReadCache<WeatherReport>("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}&current_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<glazeOpts>(apiResp, responseBuffer); errc)
return Err(DracError(DracErrorCode::ParseError, "Failed to parse Open-Meteo JSON response"));
static constexpr Array<const char*, 9> 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;
}

View file

@ -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<WeatherReport> override;
private:
double m_lat;
double m_lon;
String m_units;
};
} // namespace weather

View file

@ -0,0 +1,184 @@
#define NOMINMAX
#include "OpenWeatherMapService.hpp"
#include <chrono>
#include <curl/curl.h>
#include <curl/easy.h>
#include <format>
#include <glaze/core/meta.hpp>
#include <glaze/json/read.hpp>
#include <matchit.hpp>
#include <variant>
#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> 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::OWMResponse::Main> : weather::OWMMainGlaze {};
template <>
struct glz::meta<weather::OWMResponse::Weather> : weather::OWMWeatherGlaze {};
template <>
struct glz::meta<weather::OWMResponse> : 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<char*>(contents), totalSize);
return totalSize;
}
fn MakeApiRequest(const weather::String& url) -> Result<WeatherReport> {
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<glaze_opts>(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<std::string>(owm.name),
.description = !owm.weather.empty() ? owm.weather[0].description : "",
.timestamp = static_cast<usize>(owm.dt),
};
return report;
}
} // namespace
OpenWeatherMapService::OpenWeatherMapService(std::variant<String, Coords> 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<WeatherReport> {
using namespace std::chrono;
if (Result<WeatherReport> data = ReadCache<WeatherReport>("weather")) {
const WeatherReport& dataVal = *data;
if (const duration<double> 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<WeatherReport>& result) -> Result<WeatherReport> {
if (!result)
return Err(result.error());
if (Result<> writeResult = WriteCache("weather", *result); !writeResult)
return Err(writeResult.error());
return *result;
};
if (std::holds_alternative<String>(m_location)) {
using util::types::i32;
const auto& city = std::get<String>(m_location);
char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<i32>(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<Coords>(m_location)) {
const auto& [lat, lon] = std::get<Coords>(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."));
}

View file

@ -0,0 +1,18 @@
#pragma once
#include <variant>
#include "IWeatherService.hpp"
namespace weather {
class OpenWeatherMapService : public IWeatherService {
public:
OpenWeatherMapService(std::variant<String, Coords> location, String apiKey, String units);
fn getWeatherInfo() const -> Result<WeatherReport> override;
private:
std::variant<String, Coords> m_location;
String m_apiKey;
String m_units;
};
} // namespace weather

View file

@ -1,7 +1,6 @@
#include "ui.hpp" #include "UI.hpp"
#include "src/os/os.hpp" #include "Util/Types.hpp"
#include "src/util/types.hpp"
namespace ui { namespace ui {
using namespace ftxui; using namespace ftxui;
@ -175,11 +174,11 @@ namespace ui {
initialRows.push_back({ .icon = calendarIcon, .label = "Date", .value = *data.date }); initialRows.push_back({ .icon = calendarIcon, .label = "Date", .value = *data.date });
if (weather.enabled && data.weather) { if (weather.enabled && data.weather) {
const weather::Output& weatherInfo = *data.weather; const weather::WeatherReport& weatherInfo = *data.weather;
String weatherValue = weather.showTownName String weatherValue = weather.showTownName && weatherInfo.name
? std::format("{}°F in {}", std::lround(weatherInfo.main.temp), weatherInfo.name) ? std::format("{}°F in {}", std::lround(weatherInfo.temperature), *weatherInfo.name)
: std::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description); : std::format("{}°F, {}", std::lround(weatherInfo.temperature), weatherInfo.description);
initialRows.push_back({ .icon = weatherIcon, .label = "Weather", .value = std::move(weatherValue) }); initialRows.push_back({ .icon = weatherIcon, .label = "Weather", .value = std::move(weatherValue) });
} else if (weather.enabled && !data.weather.has_value()) } else if (weather.enabled && !data.weather.has_value())

View file

@ -3,9 +3,9 @@
#include <ftxui/dom/elements.hpp> // ftxui::Element #include <ftxui/dom/elements.hpp> // ftxui::Element
#include <ftxui/screen/color.hpp> // ftxui::Color #include <ftxui/screen/color.hpp> // ftxui::Color
#include "src/config/config.hpp" #include "Config/Config.hpp"
#include "src/core/system_data.hpp" #include "Core/SystemData.hpp"
#include "src/util/types.hpp" #include "Util/Types.hpp"
namespace ui { namespace ui {
struct Theme { struct Theme {

View file

@ -9,10 +9,10 @@
#include <system_error> // std::error_code #include <system_error> // std::error_code
#include <type_traits> // std::decay_t #include <type_traits> // std::decay_t
#include "src/util/defs.hpp" #include "Util/Definitions.hpp"
#include "src/util/error.hpp" #include "Util/Error.hpp"
#include "src/util/logging.hpp" #include "Util/Logging.hpp"
#include "src/util/types.hpp" #include "Util/Types.hpp"
namespace util::cache { namespace util::cache {
namespace fs = std::filesystem; namespace fs = std::filesystem;
@ -40,8 +40,7 @@ namespace util::cache {
if (!fs::exists(cacheDir, errc)) { if (!fs::exists(cacheDir, errc)) {
if (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); fs::create_directories(cacheDir, errc);
@ -162,8 +161,7 @@ namespace util::cache {
if (!ofs) { if (!ofs) {
std::error_code removeEc; std::error_code removeEc;
fs::remove(tempPath, 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()));
);
} }
} }

View file

@ -1,12 +1,15 @@
#pragma once #pragma once
#include "src/util/defs.hpp" #ifdef _WIN32
#include "src/util/error.hpp" #include <stdlib.h> // NOLINT(*-deprecated-headers)
#include "src/util/types.hpp" #endif
#include "Util/Definitions.hpp"
#include "Util/Error.hpp"
#include "Util/Types.hpp"
namespace util::helpers { namespace util::helpers {
using error::DracError, error::DracErrorCode; using types::Result, types::String, types::CStr;
using types::Result, types::String, types::CStr, types::Err;
/** /**
* @brief Safely retrieves an environment variable. * @brief Safely retrieves an environment variable.
@ -15,6 +18,9 @@ namespace util::helpers {
* or an EnvError if an error occurred. * or an EnvError if an error occurred.
*/ */
[[nodiscard]] inline fn GetEnv(CStr name) -> Result<String> { [[nodiscard]] inline fn GetEnv(CStr name) -> Result<String> {
using error::DracError, error::DracErrorCode;
using types::Err;
#ifdef _WIN32 #ifdef _WIN32
using types::i32, types::usize, types::UniquePointer; using types::i32, types::usize, types::UniquePointer;

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <expected> // std::{unexpected, expected} #include <expected> // std::{unexpected, expected}
#include <format> // std::format #include <matchit.hpp> // matchit::{match, is, or_, _}
#include <source_location> // std::source_location #include <source_location> // std::source_location
#include <system_error> // std::error_code #include <system_error> // std::error_code
@ -9,11 +9,11 @@
#include <guiddef.h> // GUID #include <guiddef.h> // GUID
#include <winerror.h> // error values #include <winerror.h> // error values
#include <winrt/base.h> // winrt::hresult_error #include <winrt/base.h> // winrt::hresult_error
#else
#include <format> // std::format
#endif #endif
#include "src/util/types.hpp" #include "Util/Types.hpp"
#include "include/matchit.hpp"
namespace util { namespace util {
namespace error { namespace error {
@ -48,15 +48,15 @@ namespace util {
struct DracError { struct DracError {
// ReSharper disable CppDFANotInitializedField // ReSharper disable CppDFANotInitializedField
String message; ///< A descriptive error message, potentially including platform details. 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). 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 // ReSharper restore CppDFANotInitializedField
DracError(const DracErrorCode errc, String msg, const std::source_location& loc = std::source_location::current()) 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()) 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()) explicit DracError(const std::error_code& errc, const std::source_location& loc = std::source_location::current())
: message(errc.message()), location(loc) { : message(errc.message()), location(loc) {

View file

@ -17,9 +17,9 @@
#include <source_location> // std::source_location #include <source_location> // std::source_location
#endif #endif
#include "src/util/defs.hpp" #include "Util/Definitions.hpp"
#include "src/util/error.hpp" #include "Util/Error.hpp"
#include "src/util/types.hpp" #include "Util/Types.hpp"
namespace util::logging { namespace util::logging {
using types::usize, types::u8, types::i32, types::i64, types::CStr, types::String, types::StringView, types::Array, using types::usize, types::u8, types::i32, types::i64, types::CStr, types::String, types::StringView, types::Array,

View file

@ -1,124 +0,0 @@
#define NOMINMAX
#include "weather.hpp"
#include <chrono> // std::chrono::{duration, operator-}
#include <curl/curl.h> // curl_easy_setopt
#include <curl/easy.h> // curl_easy_init, curl_easy_perform, curl_easy_cleanup
#include <expected> // std::{expected (Result), unexpected (Err)}
#include <format> // std::format
#include <glaze/beve/read.hpp> // glz::read_beve
#include <glaze/beve/write.hpp> // glz::write_beve
#include <glaze/core/context.hpp> // glz::{error_ctx, error_code}
#include <glaze/core/opts.hpp> // glz::opts
#include <glaze/core/reflect.hpp> // glz::format_error
#include <glaze/json/read.hpp> // NOLINT(misc-include-cleaner) - glaze/json/read.hpp is needed for glz::read<glz::opts>
#include <variant> // 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<char*>(contents), totalSize);
return totalSize;
}
fn MakeApiRequest(const String& url) -> Result<Output> {
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<glaze_opts>(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<Output> {
using namespace std::chrono;
using util::types::i32;
if (Result<Output> data = ReadCache<Output>("weather")) {
const Output& dataVal = *data;
if (const duration<double> cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt));
cacheAge < 60min) // NOLINT(misc-include-cleaner) - inherited from <chrono>
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<Output>& result) -> Result<Output> {
if (!result)
return Err(result.error());
if (Result writeResult = WriteCache("weather", *result); !writeResult)
return Err(writeResult.error());
return *result;
};
if (std::holds_alternative<String>(location)) {
const auto& city = std::get<String>(location);
char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<i32>(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<Coords>(location)) {
const auto& [lat, lon] = std::get<Coords>(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."));
}

View file

@ -1,78 +0,0 @@
#pragma once
#include <glaze/core/common.hpp> // object
#include <glaze/core/meta.hpp> // 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<Condition> 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

View file

@ -1,23 +1,21 @@
#include <argparse.hpp> // argparse::ArgumentParser
#include <cstdlib> // EXIT_FAILURE, EXIT_SUCCESS #include <cstdlib> // EXIT_FAILURE, EXIT_SUCCESS
#include <ftxui/dom/elements.hpp> // ftxui::{Element, hbox, vbox, text, separator, filler, etc.} #include <ftxui/dom/elements.hpp> // ftxui::{Element, hbox, vbox, text, separator, filler, etc.}
#include <ftxui/dom/node.hpp> // ftxui::{Render} #include <ftxui/dom/node.hpp> // ftxui::{Render}
#include <ftxui/screen/screen.hpp> // ftxui::{Screen, Dimension::Full} #include <ftxui/screen/screen.hpp> // ftxui::{Screen, Dimension::Full}
#include "src/ui/ui.hpp"
#ifdef __cpp_lib_print #ifdef __cpp_lib_print
#include <print> // std::print #include <print> // std::print
#else #else
#include <iostream> // std::cout #include <iostream> // std::cout
#endif #endif
#include "src/config/config.hpp" #include "Config/Config.hpp"
#include "src/core/system_data.hpp" #include "Core/SystemData.hpp"
#include "src/util/defs.hpp" #include "UI/UI.hpp"
#include "src/util/logging.hpp" #include "Util/Definitions.hpp"
#include "src/util/types.hpp" #include "Util/Logging.hpp"
#include "Util/Types.hpp"
#include "include/argparse.hpp"
using util::types::i32, util::types::Exception; using util::types::i32, util::types::Exception;
@ -27,45 +25,49 @@ fn main(const i32 argc, char* argv[]) -> i32 {
winrt::init_apartment(); winrt::init_apartment();
#endif #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<bool>("-V").value_or(false) || parser.get<bool>("--verbose").value_or(false);
Result logLevelStr = verbose ? "debug" : parser.get<String>("--log-level");
{ {
using matchit::match, matchit::is, matchit::_; using argparse::ArgumentParser;
using util::logging::LogLevel;
using enum util::logging::LogLevel;
LogLevel minLevel = match(logLevelStr)( ArgumentParser parser("draconis", "0.1.0");
is | "debug" = Debug,
is | "info" = Info, parser
is | "warn" = Warn, .add_argument("--log-level")
is | "error" = Error, .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<bool>("-V").value_or(false) || parser.get<bool>("--verbose").value_or(false);
Result logLevelStr = verbose ? "debug" : parser.get<String>("--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 #ifndef NDEBUG
is | _ = Debug is | _ = Debug
#else #else
is | _ = Info is | _ = Info
#endif #endif
); );
SetRuntimeLogLevel(minLevel); SetRuntimeLogLevel(minLevel);
}
} }
const Config& config = Config::getInstance(); const Config& config = Config::getInstance();