lots of new stuff waow
This commit is contained in:
parent
bb4ccb5d42
commit
867bab1050
38 changed files with 704 additions and 452 deletions
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
106
meson.build
106
meson.build
|
@ -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,
|
||||||
)
|
)
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -105,6 +107,7 @@ struct Weather {
|
||||||
|
|
||||||
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.
|
||||||
|
@ -113,9 +116,7 @@ struct Weather {
|
||||||
*/
|
*/
|
||||||
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)
|
||||||
|
@ -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>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -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;
|
||||||
|
|
||||||
|
@ -83,8 +82,10 @@ namespace os {
|
||||||
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
|
|
@ -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
|
||||||
|
@ -83,7 +84,7 @@ namespace os {
|
||||||
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.
|
|
@ -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
|
|
@ -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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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."); }
|
|
@ -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
49
src/Services/Weather.hpp
Normal 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
|
24
src/Services/Weather/IWeatherService.hpp
Normal file
24
src/Services/Weather/IWeatherService.hpp
Normal 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
|
156
src/Services/Weather/OpenMeteoService.cpp
Normal file
156
src/Services/Weather/OpenMeteoService.cpp
Normal 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}¤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<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;
|
||||||
|
}
|
16
src/Services/Weather/OpenMeteoService.hpp
Normal file
16
src/Services/Weather/OpenMeteoService.hpp
Normal 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
|
184
src/Services/Weather/OpenWeatherMapService.cpp
Normal file
184
src/Services/Weather/OpenWeatherMapService.cpp
Normal 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."));
|
||||||
|
}
|
18
src/Services/Weather/OpenWeatherMapService.hpp
Normal file
18
src/Services/Weather/OpenWeatherMapService.hpp
Normal 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
|
|
@ -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())
|
|
@ -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 {
|
|
@ -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()));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
|
@ -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,
|
|
@ -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."));
|
|
||||||
}
|
|
|
@ -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
|
|
22
src/main.cpp
22
src/main.cpp
|
@ -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,7 +25,10 @@ fn main(const i32 argc, char* argv[]) -> i32 {
|
||||||
winrt::init_apartment();
|
winrt::init_apartment();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
argparse::ArgumentParser parser("draconis", "0.1.0");
|
{
|
||||||
|
using argparse::ArgumentParser;
|
||||||
|
|
||||||
|
ArgumentParser parser("draconis", "0.1.0");
|
||||||
|
|
||||||
parser
|
parser
|
||||||
.add_argument("--log-level")
|
.add_argument("--log-level")
|
||||||
|
@ -67,6 +68,7 @@ fn main(const i32 argc, char* argv[]) -> i32 {
|
||||||
|
|
||||||
SetRuntimeLogLevel(minLevel);
|
SetRuntimeLogLevel(minLevel);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const Config& config = Config::getInstance();
|
const Config& config = Config::getInstance();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue