2022-10-05 08:41:05 -04:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include "../DefaultInclude.hpp"
|
|
|
|
#include <fs/filesystem.hpp>
|
|
|
|
#include "Result.hpp"
|
|
|
|
#include "json.hpp"
|
2022-10-12 17:22:43 -04:00
|
|
|
#include <mutex>
|
2022-10-05 08:41:05 -04:00
|
|
|
|
|
|
|
namespace geode::utils::web {
|
|
|
|
using FileProgressCallback = std::function<bool(double, double)>;
|
|
|
|
|
2022-10-12 17:22:43 -04:00
|
|
|
/**
|
|
|
|
* Synchronously fetch data from the internet
|
|
|
|
* @param url URL to fetch
|
|
|
|
* @returns Returned data as bytes, or error on error
|
|
|
|
*/
|
|
|
|
GEODE_DLL Result<byte_array> fetchBytes(std::string const& url);
|
|
|
|
|
2022-10-05 08:41:05 -04:00
|
|
|
/**
|
|
|
|
* Synchronously fetch data from the internet
|
|
|
|
* @param url URL to fetch
|
|
|
|
* @returns Returned data as string, or error on error
|
|
|
|
*/
|
|
|
|
GEODE_DLL Result<std::string> fetch(std::string const& url);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Syncronously download a file from the internet
|
|
|
|
* @param url URL to fetch
|
|
|
|
* @param into Path to download file into
|
|
|
|
* @param prog Progress function; first parameter is bytes downloaded so
|
|
|
|
* far, and second is total bytes to download. Return true to continue
|
|
|
|
* downloading, and false to interrupt. Note that interrupting does not
|
|
|
|
* automatically remove the file that was being downloaded
|
|
|
|
* @returns Returned data as JSON, or error on error
|
|
|
|
*/
|
|
|
|
GEODE_DLL Result<> fetchFile(
|
|
|
|
std::string const& url,
|
|
|
|
ghc::filesystem::path const& into,
|
|
|
|
FileProgressCallback prog = nullptr
|
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Synchronously fetch data from the internet and parse it as JSON
|
|
|
|
* @param url URL to fetch
|
|
|
|
* @returns Returned data as JSON, or error on error
|
|
|
|
*/
|
|
|
|
template<class Json = nlohmann::json>
|
|
|
|
Result<Json> fetchJSON(std::string const& url) {
|
|
|
|
auto res = fetch(url);
|
|
|
|
if (!res) return Err(res.error());
|
|
|
|
try {
|
|
|
|
return Ok(Json::parse(res.value()));
|
|
|
|
} catch(std::exception& e) {
|
|
|
|
return Err(e.what());
|
|
|
|
}
|
|
|
|
}
|
2022-10-12 17:22:43 -04:00
|
|
|
|
|
|
|
class SentAsyncWebRequest;
|
|
|
|
template<class T>
|
|
|
|
class AsyncWebResult;
|
|
|
|
class AsyncWebResponse;
|
|
|
|
class AsyncWebRequest;
|
|
|
|
|
|
|
|
using AsyncProgress = std::function<void(SentAsyncWebRequest&, double, double)>;
|
|
|
|
using AsyncExpect = std::function<void(std::string const&)>;
|
|
|
|
using AsyncThen = std::function<void(SentAsyncWebRequest&, byte_array const&)>;
|
|
|
|
using AsyncCancelled = std::function<void(SentAsyncWebRequest&)>;
|
|
|
|
|
|
|
|
class SentAsyncWebRequest {
|
|
|
|
private:
|
|
|
|
std::string m_id;
|
|
|
|
std::string m_url;
|
|
|
|
std::vector<AsyncThen> m_thens;
|
|
|
|
std::vector<AsyncExpect> m_expects;
|
|
|
|
std::vector<AsyncProgress> m_progresses;
|
|
|
|
std::vector<AsyncCancelled> m_cancelleds;
|
|
|
|
std::atomic<bool> m_paused = true;
|
|
|
|
std::atomic<bool> m_cancelled = false;
|
|
|
|
std::atomic<bool> m_finished = false;
|
|
|
|
std::atomic<bool> m_cleanedUp = false;
|
|
|
|
mutable std::mutex m_mutex;
|
|
|
|
std::variant<
|
|
|
|
std::monostate,
|
|
|
|
std::ostream*,
|
|
|
|
ghc::filesystem::path
|
|
|
|
> m_target = std::monostate();
|
|
|
|
|
|
|
|
template<class T>
|
|
|
|
friend class AsyncWebResult;
|
|
|
|
friend class AsyncWebRequest;
|
|
|
|
|
|
|
|
void pause();
|
|
|
|
void resume();
|
|
|
|
void error(std::string const& error);
|
|
|
|
void doCancel();
|
|
|
|
|
|
|
|
public:
|
|
|
|
SentAsyncWebRequest(AsyncWebRequest const&, std::string const& id);
|
|
|
|
|
|
|
|
void cancel();
|
|
|
|
bool finished() const;
|
|
|
|
};
|
|
|
|
|
|
|
|
using SentAsyncWebRequestHandle = std::shared_ptr<SentAsyncWebRequest>;
|
|
|
|
|
|
|
|
template<class T>
|
|
|
|
using DataConverter = Result<T>(*)(byte_array const&);
|
|
|
|
|
2022-10-12 21:50:41 -04:00
|
|
|
class GEODE_DLL AsyncWebRequest {
|
|
|
|
private:
|
|
|
|
std::optional<std::string> m_joinID;
|
|
|
|
std::string m_url;
|
|
|
|
AsyncThen m_then = nullptr;
|
|
|
|
AsyncExpect m_expect = nullptr;
|
|
|
|
AsyncProgress m_progress = nullptr;
|
|
|
|
AsyncCancelled m_cancelled = nullptr;
|
|
|
|
bool m_sent = false;
|
|
|
|
std::variant<
|
|
|
|
std::monostate,
|
|
|
|
std::ostream*,
|
|
|
|
ghc::filesystem::path
|
|
|
|
> m_target;
|
|
|
|
|
|
|
|
template<class T>
|
|
|
|
friend class AsyncWebResult;
|
|
|
|
friend class SentAsyncWebRequest;
|
|
|
|
friend class AsyncWebResponse;
|
|
|
|
|
|
|
|
public:
|
|
|
|
AsyncWebRequest& join(std::string const& requestID);
|
|
|
|
AsyncWebResponse fetch(std::string const& url);
|
|
|
|
AsyncWebRequest& expect(AsyncExpect handler);
|
|
|
|
AsyncWebRequest& progress(AsyncProgress progressFunc);
|
|
|
|
// Web requests may be cancelled after they are finished (for example,
|
|
|
|
// if downloading files in bulk and one fails). In that case, handle
|
|
|
|
// freeing up the results of `then` here
|
|
|
|
AsyncWebRequest& cancelled(AsyncCancelled cancelledFunc);
|
|
|
|
SentAsyncWebRequestHandle send();
|
|
|
|
~AsyncWebRequest();
|
|
|
|
};
|
|
|
|
|
2022-10-12 17:22:43 -04:00
|
|
|
template<class T>
|
|
|
|
class AsyncWebResult {
|
|
|
|
private:
|
|
|
|
AsyncWebRequest& m_request;
|
|
|
|
DataConverter<T> m_converter;
|
|
|
|
|
|
|
|
AsyncWebResult(AsyncWebRequest& request, DataConverter<T> converter)
|
|
|
|
: m_request(request), m_converter(converter) {}
|
|
|
|
|
|
|
|
friend class AsyncWebResponse;
|
|
|
|
|
|
|
|
public:
|
2022-10-13 09:36:36 -04:00
|
|
|
AsyncWebRequest& then(std::function<void(T)> handle);
|
|
|
|
AsyncWebRequest& then(std::function<void(SentAsyncWebRequest&, T)> handle);
|
2022-10-12 17:22:43 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
class GEODE_DLL AsyncWebResponse {
|
|
|
|
private:
|
|
|
|
AsyncWebRequest& m_request;
|
|
|
|
|
|
|
|
inline AsyncWebResponse(AsyncWebRequest& request) : m_request(request) {}
|
|
|
|
|
|
|
|
friend class AsyncWebRequest;
|
|
|
|
|
|
|
|
public:
|
|
|
|
// Make sure the stream lives for the entire duration of the request.
|
|
|
|
AsyncWebResult<std::monostate> into(std::ostream& stream);
|
|
|
|
AsyncWebResult<std::monostate> into(ghc::filesystem::path const& path);
|
|
|
|
AsyncWebResult<std::string> text();
|
|
|
|
AsyncWebResult<byte_array> bytes();
|
|
|
|
AsyncWebResult<nlohmann::json> json();
|
|
|
|
|
|
|
|
template<class T>
|
|
|
|
AsyncWebResult<T> as(DataConverter<T> converter) {
|
|
|
|
return AsyncWebResult(m_request, converter);
|
|
|
|
}
|
|
|
|
};
|
2022-10-13 09:36:36 -04:00
|
|
|
|
|
|
|
template<class T>
|
|
|
|
AsyncWebRequest& AsyncWebResult<T>::then(std::function<void(T)> handle) {
|
|
|
|
m_request.m_then = [
|
|
|
|
converter = m_converter,
|
|
|
|
handle
|
|
|
|
](SentAsyncWebRequest& req, byte_array const& arr) {
|
|
|
|
auto conv = converter(arr);
|
|
|
|
if (conv) {
|
|
|
|
handle(conv.value());
|
|
|
|
} else {
|
|
|
|
req.error("Unable to convert value: " + conv.error());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return m_request;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<class T>
|
|
|
|
AsyncWebRequest& AsyncWebResult<T>::then(std::function<void(SentAsyncWebRequest&, T)> handle) {
|
|
|
|
m_request.m_then = [
|
|
|
|
converter = m_converter,
|
|
|
|
handle
|
|
|
|
](SentAsyncWebRequest& req, byte_array const& arr) {
|
|
|
|
auto conv = converter(arr);
|
|
|
|
if (conv) {
|
|
|
|
handle(req, conv.value());
|
|
|
|
} else {
|
|
|
|
req.error("Unable to convert value: " + conv.error());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return m_request;
|
|
|
|
}
|
2022-10-05 08:41:05 -04:00
|
|
|
}
|
|
|
|
|