sucjkl
This commit is contained in:
parent
fc69565f4d
commit
9aa2e44537
6 changed files with 397 additions and 62 deletions
100
meson.build
100
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',
|
||||||
|
@ -15,6 +15,11 @@ project(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_project_arguments(
|
||||||
|
'-DDRACONISPLUSPLUS_VERSION="' + meson.project_version() + '"',
|
||||||
|
language: ['cpp', 'objcpp'],
|
||||||
|
)
|
||||||
|
|
||||||
cpp = meson.get_compiler('cpp')
|
cpp = meson.get_compiler('cpp')
|
||||||
host_system = host_machine.system()
|
host_system = host_machine.system()
|
||||||
|
|
||||||
|
@ -35,13 +40,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 +54,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,13 +85,13 @@ else
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
add_project_arguments(common_cpp_args, language : 'cpp')
|
add_project_arguments(common_cpp_args, language: 'cpp')
|
||||||
|
|
||||||
# --------------------- #
|
# --------------------- #
|
||||||
# Include Directories #
|
# Include Directories #
|
||||||
# --------------------- #
|
# --------------------- #
|
||||||
project_internal_includes = include_directories('src')
|
project_internal_includes = include_directories('src')
|
||||||
project_public_includes = include_directories('include', is_system : true)
|
project_public_includes = include_directories('include', is_system: true)
|
||||||
|
|
||||||
# ------- #
|
# ------- #
|
||||||
# Files #
|
# Files #
|
||||||
|
@ -94,22 +99,23 @@ project_public_includes = include_directories('include', is_system : true)
|
||||||
base_sources = files(
|
base_sources = files(
|
||||||
'src/Config/Config.cpp',
|
'src/Config/Config.cpp',
|
||||||
'src/Core/SystemData.cpp',
|
'src/Core/SystemData.cpp',
|
||||||
|
'src/Services/PackageCounting.cpp',
|
||||||
|
'src/Services/Weather/MetNoService.cpp',
|
||||||
'src/Services/Weather/OpenMeteoService.cpp',
|
'src/Services/Weather/OpenMeteoService.cpp',
|
||||||
'src/Services/Weather/OpenWeatherMapService.cpp',
|
'src/Services/Weather/OpenWeatherMapService.cpp',
|
||||||
'src/Services/PackageCounting.cpp',
|
|
||||||
'src/UI/UI.cpp',
|
'src/UI/UI.cpp',
|
||||||
'src/main.cpp',
|
'src/main.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, []))
|
||||||
|
@ -118,9 +124,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
|
||||||
|
@ -131,8 +137,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'),
|
||||||
]
|
]
|
||||||
|
@ -143,11 +149,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'),
|
||||||
|
@ -156,15 +162,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
|
||||||
|
|
||||||
|
@ -172,28 +178,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
|
||||||
|
@ -219,9 +225,9 @@ endif
|
||||||
executable(
|
executable(
|
||||||
'draconis++',
|
'draconis++',
|
||||||
sources,
|
sources,
|
||||||
include_directories : [project_internal_includes, project_public_includes],
|
include_directories: [project_internal_includes, project_public_includes],
|
||||||
objc_args : objc_args,
|
objc_args: objc_args,
|
||||||
link_args : link_args,
|
link_args: link_args,
|
||||||
dependencies : deps,
|
dependencies: deps,
|
||||||
install : true,
|
install: true,
|
||||||
)
|
)
|
|
@ -15,6 +15,7 @@
|
||||||
#include "Util/Env.hpp"
|
#include "Util/Env.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "../Services/Weather/MetNoService.hpp"
|
||||||
#include "../Services/Weather/OpenMeteoService.hpp"
|
#include "../Services/Weather/OpenMeteoService.hpp"
|
||||||
#include "../Services/Weather/OpenWeatherMapService.hpp"
|
#include "../Services/Weather/OpenWeatherMapService.hpp"
|
||||||
#include "Services/Weather.hpp"
|
#include "Services/Weather.hpp"
|
||||||
|
@ -155,8 +156,19 @@ struct Weather {
|
||||||
error_log("OpenMeteo requires coordinates for location.");
|
error_log("OpenMeteo requires coordinates for location.");
|
||||||
weather.enabled = false;
|
weather.enabled = false;
|
||||||
}
|
}
|
||||||
|
} else if (provider == "metno") {
|
||||||
|
if (std::holds_alternative<weather::Coords>(weather.location)) {
|
||||||
|
const auto& coords = std::get<weather::Coords>(weather.location);
|
||||||
|
weather.service = std::make_unique<weather::MetNoService>(coords.lat, coords.lon, weather.units);
|
||||||
} else {
|
} else {
|
||||||
|
error_log("MetNo requires coordinates for location.");
|
||||||
|
weather.enabled = false;
|
||||||
|
}
|
||||||
|
} else if (provider == "openweathermap") {
|
||||||
weather.service = std::make_unique<weather::OpenWeatherMapService>(weather.location, weather.apiKey, weather.units);
|
weather.service = std::make_unique<weather::OpenWeatherMapService>(weather.location, weather.apiKey, weather.units);
|
||||||
|
} else {
|
||||||
|
error_log("Unknown weather provider: {}", provider);
|
||||||
|
weather.enabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,9 +68,10 @@ namespace {
|
||||||
|
|
||||||
namespace os {
|
namespace os {
|
||||||
SystemData::SystemData(const Config& config) {
|
SystemData::SystemData(const Config& config) {
|
||||||
using enum std::launch;
|
|
||||||
using package::GetTotalCount;
|
using package::GetTotalCount;
|
||||||
using util::types::Future, util::types::Err;
|
using util::types::Future, util::types::Err;
|
||||||
|
using weather::WeatherReport;
|
||||||
|
using enum std::launch;
|
||||||
using enum util::error::DracErrorCode;
|
using enum util::error::DracErrorCode;
|
||||||
|
|
||||||
Future<Result<String>> hostFut = std::async(async, GetHost);
|
Future<Result<String>> hostFut = std::async(async, GetHost);
|
||||||
|
@ -83,7 +84,7 @@ 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::WeatherReport>> wthrFut = std::async(config.weather.enabled ? async : deferred, [&config]() -> Result<weather::WeatherReport> {
|
Future<Result<WeatherReport>> wthrFut = std::async(config.weather.enabled ? async : deferred, [&config]() -> Result<WeatherReport> {
|
||||||
return config.weather.enabled && config.weather.service
|
return config.weather.enabled && config.weather.service
|
||||||
? config.weather.service->getWeatherInfo()
|
? config.weather.service->getWeatherInfo()
|
||||||
: Err(DracError(ApiUnavailable, "Weather API disabled"));
|
: Err(DracError(ApiUnavailable, "Weather API disabled"));
|
||||||
|
|
300
src/Services/Weather/MetNoService.cpp
Normal file
300
src/Services/Weather/MetNoService.cpp
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
#define NOMINMAX
|
||||||
|
|
||||||
|
#include "MetNoService.hpp"
|
||||||
|
|
||||||
|
#include <chrono> // std::chrono::{system_clock, minutes, seconds}
|
||||||
|
#include <curl/curl.h> // CURL, CURLcode, CURLOPT_*, CURLE_OK
|
||||||
|
#include <curl/easy.h> // curl_easy_init, curl_easy_setopt, curl_easy_perform, curl_easy_strerror, curl_easy_cleanup
|
||||||
|
#include <format> // std::format
|
||||||
|
#include <glaze/json/read.hpp> // glz::read
|
||||||
|
#include <sstream> // std::istringstream
|
||||||
|
#include <unordered_map> // std::unordered_map
|
||||||
|
|
||||||
|
#include "Util/Caching.hpp"
|
||||||
|
#include "Util/Error.hpp"
|
||||||
|
#include "Util/Types.hpp"
|
||||||
|
|
||||||
|
using weather::MetNoService;
|
||||||
|
using weather::WeatherReport;
|
||||||
|
|
||||||
|
namespace weather {
|
||||||
|
using util::types::f64, util::types::i32, util::types::String, util::types::usize, util::logging::Option;
|
||||||
|
|
||||||
|
struct MetNoTimeseriesDetails {
|
||||||
|
f64 airTemperature;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoTimeseriesNext1hSummary {
|
||||||
|
String symbolCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoTimeseriesNext1h {
|
||||||
|
MetNoTimeseriesNext1hSummary summary;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoTimeseriesInstant {
|
||||||
|
MetNoTimeseriesDetails details;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoTimeseriesData {
|
||||||
|
MetNoTimeseriesInstant instant;
|
||||||
|
Option<MetNoTimeseriesNext1h> next1Hours;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoTimeseries {
|
||||||
|
String time;
|
||||||
|
MetNoTimeseriesData data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoProperties {
|
||||||
|
Vec<MetNoTimeseries> timeseries;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoResponse {
|
||||||
|
MetNoProperties properties;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoTimeseriesDetailsGlaze {
|
||||||
|
using T = MetNoTimeseriesDetails;
|
||||||
|
|
||||||
|
static constexpr auto value = glz::object("air_temperature", &T::airTemperature);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoTimeseriesNext1hSummaryGlaze {
|
||||||
|
using T = MetNoTimeseriesNext1hSummary;
|
||||||
|
|
||||||
|
static constexpr auto value = glz::object("symbol_code", &T::symbolCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoTimeseriesNext1hGlaze {
|
||||||
|
using T = MetNoTimeseriesNext1h;
|
||||||
|
|
||||||
|
static constexpr auto value = glz::object("summary", &T::summary);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoTimeseriesInstantGlaze {
|
||||||
|
using T = MetNoTimeseriesInstant;
|
||||||
|
static constexpr auto value = glz::object("details", &T::details);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoTimeseriesDataGlaze {
|
||||||
|
using T = MetNoTimeseriesData;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
static constexpr auto value = glz::object(
|
||||||
|
"instant", &T::instant,
|
||||||
|
"next_1_hours", &T::next1Hours
|
||||||
|
);
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoTimeseriesGlaze {
|
||||||
|
using T = MetNoTimeseries;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
static constexpr auto value = glz::object(
|
||||||
|
"time", &T::time,
|
||||||
|
"data", &T::data
|
||||||
|
);
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoPropertiesGlaze {
|
||||||
|
using T = MetNoProperties;
|
||||||
|
|
||||||
|
static constexpr auto value = glz::object("timeseries", &T::timeseries);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetNoResponseGlaze {
|
||||||
|
using T = MetNoResponse;
|
||||||
|
|
||||||
|
static constexpr auto value = glz::object("properties", &T::properties);
|
||||||
|
};
|
||||||
|
} // namespace weather
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct glz::meta<weather::MetNoTimeseriesDetails> : weather::MetNoTimeseriesDetailsGlaze {};
|
||||||
|
template <>
|
||||||
|
struct glz::meta<weather::MetNoTimeseriesNext1hSummary> : weather::MetNoTimeseriesNext1hSummaryGlaze {};
|
||||||
|
template <>
|
||||||
|
struct glz::meta<weather::MetNoTimeseriesNext1h> : weather::MetNoTimeseriesNext1hGlaze {};
|
||||||
|
template <>
|
||||||
|
struct glz::meta<weather::MetNoTimeseriesInstant> : weather::MetNoTimeseriesInstantGlaze {};
|
||||||
|
template <>
|
||||||
|
struct glz::meta<weather::MetNoTimeseriesData> : weather::MetNoTimeseriesDataGlaze {};
|
||||||
|
template <>
|
||||||
|
struct glz::meta<weather::MetNoTimeseries> : weather::MetNoTimeseriesGlaze {};
|
||||||
|
template <>
|
||||||
|
struct glz::meta<weather::MetNoProperties> : weather::MetNoPropertiesGlaze {};
|
||||||
|
template <>
|
||||||
|
struct glz::meta<weather::MetNoResponse> : weather::MetNoResponseGlaze {};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using glz::opts;
|
||||||
|
using util::error::DracError, util::error::DracErrorCode;
|
||||||
|
using util::types::usize, util::types::Err, util::types::String;
|
||||||
|
|
||||||
|
constexpr opts glazeOpts = { .error_on_unknown_keys = false };
|
||||||
|
|
||||||
|
fn SYMBOL_DESCRIPTIONS() -> const std::unordered_map<String, String>& {
|
||||||
|
static const std::unordered_map<String, String> MAP = {
|
||||||
|
{ "clearsky_day", "clear sky" },
|
||||||
|
{ "clearsky_night", "clear sky" },
|
||||||
|
{ "clearsky_polartwilight", "clear sky" },
|
||||||
|
{ "cloudy", "cloudy" },
|
||||||
|
{ "fair_day", "fair" },
|
||||||
|
{ "fair_night", "fair" },
|
||||||
|
{ "fair_polartwilight", "fair" },
|
||||||
|
{ "fog", "fog" },
|
||||||
|
{ "heavyrain", "heavy rain" },
|
||||||
|
{ "heavyrainandthunder", "heavy rain and thunder" },
|
||||||
|
{ "heavyrainshowers_day", "heavy rain showers" },
|
||||||
|
{ "heavyrainshowers_night", "heavy rain showers" },
|
||||||
|
{ "heavyrainshowers_polartwilight", "heavy rain showers" },
|
||||||
|
{ "heavysleet", "heavy sleet" },
|
||||||
|
{ "heavysleetandthunder", "heavy sleet and thunder" },
|
||||||
|
{ "heavysleetshowers_day", "heavy sleet showers" },
|
||||||
|
{ "heavysleetshowers_night", "heavy sleet showers" },
|
||||||
|
{ "heavysleetshowers_polartwilight", "heavy sleet showers" },
|
||||||
|
{ "heavysnow", "heavy snow" },
|
||||||
|
{ "heavysnowandthunder", "heavy snow and thunder" },
|
||||||
|
{ "heavysnowshowers_day", "heavy snow showers" },
|
||||||
|
{ "heavysnowshowers_night", "heavy snow showers" },
|
||||||
|
{ "heavysnowshowers_polartwilight", "heavy snow showers" },
|
||||||
|
{ "lightrain", "light rain" },
|
||||||
|
{ "lightrainandthunder", "light rain and thunder" },
|
||||||
|
{ "lightrainshowers_day", "light rain showers" },
|
||||||
|
{ "lightrainshowers_night", "light rain showers" },
|
||||||
|
{ "lightrainshowers_polartwilight", "light rain showers" },
|
||||||
|
{ "lightsleet", "light sleet" },
|
||||||
|
{ "lightsleetandthunder", "light sleet and thunder" },
|
||||||
|
{ "lightsleetshowers_day", "light sleet showers" },
|
||||||
|
{ "lightsleetshowers_night", "light sleet showers" },
|
||||||
|
{ "lightsleetshowers_polartwilight", "light sleet showers" },
|
||||||
|
{ "lightsnow", "light snow" },
|
||||||
|
{ "lightsnowandthunder", "light snow and thunder" },
|
||||||
|
{ "lightsnowshowers_day", "light snow showers" },
|
||||||
|
{ "lightsnowshowers_night", "light snow showers" },
|
||||||
|
{ "lightsnowshowers_polartwilight", "light snow showers" },
|
||||||
|
{ "partlycloudy_day", "partly cloudy" },
|
||||||
|
{ "partlycloudy_night", "partly cloudy" },
|
||||||
|
{ "partlycloudy_polartwilight", "partly cloudy" },
|
||||||
|
{ "rain", "rain" },
|
||||||
|
{ "rainandthunder", "rain and thunder" },
|
||||||
|
{ "rainshowers_day", "rain showers" },
|
||||||
|
{ "rainshowers_night", "rain showers" },
|
||||||
|
{ "rainshowers_polartwilight", "rain showers" },
|
||||||
|
{ "sleet", "sleet" },
|
||||||
|
{ "sleetandthunder", "sleet and thunder" },
|
||||||
|
{ "sleetshowers_day", "sleet showers" },
|
||||||
|
{ "sleetshowers_night", "sleet showers" },
|
||||||
|
{ "sleetshowers_polartwilight", "sleet showers" },
|
||||||
|
{ "snow", "snow" },
|
||||||
|
{ "snowandthunder", "snow and thunder" },
|
||||||
|
{ "snowshowers_day", "snow showers" },
|
||||||
|
{ "snowshowers_night", "snow showers" },
|
||||||
|
{ "snowshowers_polartwilight", "snow showers" },
|
||||||
|
{ "unknown", "unknown" }
|
||||||
|
};
|
||||||
|
|
||||||
|
return MAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn WriteCallback(void* contents, usize size, usize nmemb, String* str) -> usize {
|
||||||
|
usize totalSize = size * nmemb;
|
||||||
|
str->append(static_cast<char*>(contents), totalSize);
|
||||||
|
return totalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_iso8601_to_epoch(const String& iso8601) -> usize {
|
||||||
|
std::tm time = {};
|
||||||
|
std::istringstream stream(iso8601);
|
||||||
|
|
||||||
|
stream >> std::get_time(&time, "%Y-%m-%dT%H:%M:%SZ");
|
||||||
|
|
||||||
|
if (stream.fail())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
return static_cast<util::types::usize>(_mkgmtime(&time));
|
||||||
|
#else
|
||||||
|
return static_cast<util::types::usize>(timegm(&time));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
MetNoService::MetNoService(f64 lat, f64 lon, String units)
|
||||||
|
: m_lat(lat), m_lon(lon), m_units(std::move(units)) {}
|
||||||
|
|
||||||
|
fn MetNoService::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::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.met.no/weatherapi/locationforecast/2.0/compact?lat={:.4f}&lon={:.4f}", m_lat, m_lon);
|
||||||
|
|
||||||
|
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);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "draconisplusplus/" DRACONISPLUSPLUS_VERSION " git.pupbrained.xyz/draconisplusplus");
|
||||||
|
|
||||||
|
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::MetNoResponse apiResp {};
|
||||||
|
|
||||||
|
if (error_ctx errc = read<glazeOpts>(apiResp, responseBuffer); errc)
|
||||||
|
return Err(DracError(DracErrorCode::ParseError, "Failed to parse met.no JSON response"));
|
||||||
|
|
||||||
|
if (apiResp.properties.timeseries.empty())
|
||||||
|
return Err(DracError(DracErrorCode::ParseError, "No timeseries data in met.no response"));
|
||||||
|
|
||||||
|
const MetNoTimeseries& first = apiResp.properties.timeseries.front();
|
||||||
|
|
||||||
|
f64 temp = first.data.instant.details.airTemperature;
|
||||||
|
|
||||||
|
if (m_units == "imperial")
|
||||||
|
temp = temp * 9.0 / 5.0 + 32.0;
|
||||||
|
|
||||||
|
String symbolCode = first.data.next1Hours ? first.data.next1Hours->summary.symbolCode : "";
|
||||||
|
String description = symbolCode;
|
||||||
|
|
||||||
|
if (!symbolCode.empty()) {
|
||||||
|
auto iter = SYMBOL_DESCRIPTIONS().find(symbolCode);
|
||||||
|
|
||||||
|
if (iter != SYMBOL_DESCRIPTIONS().end())
|
||||||
|
description = iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
WeatherReport out = {
|
||||||
|
.temperature = temp,
|
||||||
|
.name = None,
|
||||||
|
.description = description,
|
||||||
|
.timestamp = parse_iso8601_to_epoch(first.time),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Result<> writeResult = WriteCache("weather", out); !writeResult)
|
||||||
|
return Err(writeResult.error());
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
16
src/Services/Weather/MetNoService.hpp
Normal file
16
src/Services/Weather/MetNoService.hpp
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IWeatherService.hpp"
|
||||||
|
|
||||||
|
namespace weather {
|
||||||
|
class MetNoService : public IWeatherService {
|
||||||
|
public:
|
||||||
|
MetNoService(f64 lat, f64 lon, String units = "metric");
|
||||||
|
[[nodiscard]] fn getWeatherInfo() const -> Result<WeatherReport> override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
f64 m_lat;
|
||||||
|
f64 m_lon;
|
||||||
|
String m_units;
|
||||||
|
};
|
||||||
|
} // namespace weather
|
|
@ -9,8 +9,8 @@ namespace weather {
|
||||||
[[nodiscard]] fn getWeatherInfo() const -> Result<WeatherReport> override;
|
[[nodiscard]] fn getWeatherInfo() const -> Result<WeatherReport> override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
double m_lat;
|
f64 m_lat;
|
||||||
double m_lon;
|
f64 m_lon;
|
||||||
String m_units;
|
String m_units;
|
||||||
};
|
};
|
||||||
} // namespace weather
|
} // namespace weather
|
Loading…
Add table
Add a link
Reference in a new issue