Skip to content

Commit

Permalink
Merged libcurlwrapper and timezone service changes.
Browse files Browse the repository at this point in the history
--HG--
branch : upstream
  • Loading branch information
Daniel K. O. (dkosmari) committed Jun 6, 2024
1 parent 12119f5 commit 8b92a0c
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 189 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
FROM devkitpro/devkitppc

COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20240505 /artifacts $DEVKITPRO
COPY --from=ghcr.io/wiiu-env/libcurlwrapper:20240505 /artifacts $DEVKITPRO
COPY --from=ghcr.io/wiiu-env/libnotifications:20240426 /artifacts $DEVKITPRO
COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20240505 /artifacts $DEVKITPRO

RUN git config --global --add safe.directory /project

Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ LDFLAGS = -g \
-Wl,-Map,$(notdir $*.map) \
$(CXXFLAGS)

LIBS := -lnotifications -lwups -lwut
LIBS := -lcurlwrapper -lnotifications -lwups -lwut

#-------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level
# containing include and lib
#-------------------------------------------------------------------------------
LIBDIRS := $(WUMS_ROOT) $(WUPS_ROOT) $(WUT_ROOT)
LIBDIRS := $(WUMS_ROOT) $(WUPS_ROOT) $(WUT_ROOT) $(PORTLIBS)

#-------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
Expand Down
4 changes: 4 additions & 0 deletions include/cfg.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace cfg {
extern const char* sync;
extern const char* threads;
extern const char* tolerance;
extern const char* tz_service;
extern const char* utc_offset;
}

Expand All @@ -29,6 +30,7 @@ namespace cfg {
extern const char* sync;
extern const char* threads;
extern const char* tolerance;
extern const char* tz_service;
extern const char* utc_offset;
}

Expand All @@ -40,6 +42,7 @@ namespace cfg {
extern const bool sync;
extern const int threads;
extern const std::chrono::milliseconds tolerance;
extern const int tz_service;
}


Expand All @@ -50,6 +53,7 @@ namespace cfg {
extern bool sync;
extern int threads;
extern std::chrono::milliseconds tolerance;
extern int tz_service;
extern std::chrono::minutes utc_offset;


Expand Down
35 changes: 31 additions & 4 deletions include/timezone_query_item.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,46 @@

#include <memory>

#include "wupsxx/text_item.hpp"
#include "wupsxx/item.hpp"
#include "wupsxx/var_watch.hpp"


struct timezone_query_item : wups::config::text_item {
class timezone_query_item : public wups::config::item {

timezone_query_item();
// We store the geolocation option as an integer, no point in parsing any complex
// string since we need specific implementations for each service.

wups::config::var_watch<int> variable;
const int default_value;
std::string text;

public:

timezone_query_item(const std::string& key,
const std::string& label,
int& variable,
int default_value);

static
std::unique_ptr<timezone_query_item> create();
std::unique_ptr<timezone_query_item> create(const std::string& key,
const std::string& label,
int& variable,
int default_value);


int get_display(char* buf, std::size_t size) const override;

int get_selected_display(char* buf, std::size_t size) const override;

void restore() override;

void on_input(WUPSConfigSimplePadData input,
WUPS_CONFIG_SIMPLE_INPUT repeat) override;

private:

void on_changed();

void run();

};
Expand Down
10 changes: 9 additions & 1 deletion include/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,17 @@ namespace utils {
};


int
get_num_tz_services();


const char*
get_tz_service_name(int idx);


std::pair<std::string,
std::chrono::minutes>
fetch_timezone();
fetch_timezone(int idx);

} // namespace utils

Expand Down
6 changes: 6 additions & 0 deletions source/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "cfg.hpp"

#include "logging.hpp"
#include "nintendo_glyphs.h"
#include "time_utils.hpp"
#include "utils.hpp"
#include "wupsxx/storage.hpp"
Expand All @@ -26,6 +27,7 @@ namespace cfg {
const char* sync = "sync";
const char* threads = "threads";
const char* tolerance = "tolerance";
const char* tz_service = "tz_service";
const char* utc_offset = "utc_offset";
}

Expand All @@ -38,6 +40,7 @@ namespace cfg {
const char* sync = "Syncing Enabled";
const char* threads = "Background Threads";
const char* tolerance = "Tolerance";
const char* tz_service = "Detect Time Zone (press " NIN_GLYPH_BTN_A ")";
const char* utc_offset = "Time Offset (UTC)";
}

Expand All @@ -50,6 +53,7 @@ namespace cfg {
const bool sync = false;
const int threads = 4;
const milliseconds tolerance = 500ms;
const int tz_service = 0;
}


Expand All @@ -60,6 +64,7 @@ namespace cfg {
bool sync = defaults::sync;
int threads = defaults::threads;
milliseconds tolerance = defaults::tolerance;
int tz_service = defaults::tz_service;
minutes utc_offset = 0min;


Expand Down Expand Up @@ -87,6 +92,7 @@ namespace cfg {
load_or_init(key::sync, sync);
load_or_init(key::threads, threads);
load_or_init(key::tolerance, tolerance);
load_or_init(key::tz_service, tz_service);
load_or_init(key::utc_offset, utc_offset);
// logging::printf("Loaded settings.");
}
Expand Down
5 changes: 4 additions & 1 deletion source/config_screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ make_config_screen()
cfg::label::utc_offset,
cfg::utc_offset));

cat.add(timezone_query_item::create());
cat.add(timezone_query_item::create(cfg::key::tz_service,
cfg::label::tz_service,
cfg::tz_service,
cfg::defaults::tz_service));

cat.add(bool_item::create(cfg::key::auto_tz,
cfg::label::auto_tz,
Expand Down
6 changes: 3 additions & 3 deletions source/core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,17 +272,17 @@ namespace core {

if (cfg::auto_tz) {
try {
auto [name, offset] = utils::fetch_timezone();
auto [name, offset] = utils::fetch_timezone(cfg::tz_service);
if (offset != cfg::utc_offset) {
cfg::set_and_store_utc_offset(offset);
notify::info(notify::level::verbose,
"Updated timezone to " + name +
"Updated time zone to " + name +
"(" + time_utils::tz_offset_to_string(offset) + ")");
}
}
catch (std::exception& e) {
notify::error(notify::level::verbose,
"Failed to update timezone: "s + e.what());
"Failed to update time zone: "s + e.what());
}
}

Expand Down
169 changes: 9 additions & 160 deletions source/http_client.cpp
Original file line number Diff line number Diff line change
@@ -1,177 +1,26 @@
// SPDX-License-Identifier: MIT

#include <map>
#include <optional>
#include <ranges> // views::drop()
#include <regex>
#include <stdexcept> // runtime_error

#include "http_client.hpp"

#include "net/addrinfo.hpp"
#include "net/socket.hpp"
#include "utils.hpp"
#include "curl.hpp"


namespace http {

const std::string CRLF = "\r\n";


struct url_fields {
std::string protocol;
std::string host;
std::optional<int> port;
std::optional<std::string> path;
};


url_fields
parse_url(const std::string& url)
{
url_fields fields;

std::regex re{R"((https?)://([^:/\s]+)(:(\d+))?(/.*)?)",
std::regex_constants::ECMAScript};

std::smatch m;
if (!regex_match(url, m, re))
throw std::runtime_error{"failed to parse URL: \"" + url + "\""};

fields.protocol = m[1];
fields.host = m[2];
if (m[4].matched)
fields.port = std::stoi(m[4]);
if (m[5].matched)
fields.path = m[5];
return fields;
}


std::string
build_request(const url_fields& fields)
{
std::string req = "GET ";
if (fields.path)
req += *fields.path;
else
req += "/";
req += " HTTP/1.1" + CRLF;
req += "Host: " + fields.host + CRLF;
req += "User-Agent: " PLUGIN_NAME "/" PLUGIN_VERSION " (Wii U; Aroma)" + CRLF;
req += "Accept: text/plain" + CRLF;
req += "Connection: close" + CRLF;
req += CRLF;
return req;
}


struct response_header {
unsigned long length; // We only care about Content-Length.
std::string type;
};


response_header
parse_header(const std::string input)
{
auto lines = utils::split(input, CRLF);
if (lines.empty())
throw std::runtime_error{"Empty HTTP response."};

{
std::regex re{R"(HTTP/1\.1 (\d+)( (.*))?)",
std::regex_constants::ECMAScript};
std::smatch m;
if (!regex_match(lines[0], m, re))
throw std::runtime_error{"Could not parse HTTP response: \""
+ lines[0] + "\""};
int status = std::stoi(m[1]);
if (status < 200 || status > 299)
throw std::runtime_error{"HTTP status was " + m[1].str()};
}

std::map<std::string, std::string> fields;
for (const auto& line : lines | std::views::drop(1)) {
auto key_val = utils::split(line, ": ", 2);
if (key_val.size() != 2)
throw std::runtime_error{"invalid HTTP header field: " + line};
auto key = key_val[0];
auto val = key_val[1];
fields[key] = val;
}

if (!fields.contains("Content-Length"))
throw std::runtime_error{"HTTP header is missing mandatory Content-Length field."};

response_header header;
header.length = std::stoul(fields.at("Content-Length"));
header.type = fields.at("Content-Type");

return header;
}


// Not very efficient, read one byte at a time.
std::string
recv_until(net::socket& sock,
const std::string& end_token)
{
std::string result;

char buffer[1];
while (true) {
if (sock.recv(buffer, 1) == 0)
break;
result.append(buffer, 1);

// if we found the end token, remove it from the result and break out
if (result.ends_with(end_token)) {
result.resize(result.size() - end_token.size());
break;
}
}

return result;
}


std::string
get(const std::string& url)
{
auto fields = parse_url(url);

if (fields.protocol != "http")
throw std::runtime_error{"Protocol '" + fields.protocol + "' not supported."};

if (!fields.port)
fields.port = 80;

net::addrinfo::hints opts { .type = net::socket::type::tcp };
auto addresses = net::addrinfo::lookup(fields.host,
std::to_string(*fields.port),
opts);
if (addresses.empty())
throw std::runtime_error{"Host '" + fields.host + "' has no IP addresses."};

const auto& host = addresses.front();
net::socket sock{host.type};

sock.connect(host.addr);
curl::global guard;

auto request = build_request(fields);
sock.send_all(request.data(), request.size());
curl::handle handle;

auto header_str = recv_until(sock, CRLF + CRLF);
auto header = parse_header(header_str);
handle.set_useragent(PLUGIN_NAME "/" PLUGIN_VERSION " (Wii U; Aroma)");
handle.set_followlocation(true);
handle.set_url(url);

if (!header.type.starts_with("text/plain"))
throw std::runtime_error{"HTTP response is not plain text: \""
+ header.type + "\""};
handle.perform();

std::string result(header.length, '\0');
sock.recv_all(result.data(), result.size());
return result;
return handle.result;
}

}
} // namespace http
Loading

0 comments on commit 8b92a0c

Please sign in to comment.