geode/loader/include/Geode/utils/web.hpp

366 lines
14 KiB
C++
Raw Normal View History

#pragma once
#include "../DefaultInclude.hpp"
2023-02-08 10:25:07 -05:00
#include "MiniFunction.hpp"
#include <matjson.hpp>
2022-12-04 11:39:40 -05:00
#include "Result.hpp"
#include "general.hpp"
2022-10-30 14:59:20 -04:00
2023-02-08 08:42:34 -05:00
#include <ghc/fs_fwd.hpp>
#include <mutex>
namespace geode::utils::web {
GEODE_DLL void openLinkInBrowser(std::string const& url);
2022-12-04 11:39:40 -05:00
2023-02-08 10:25:07 -05:00
using FileProgressCallback = utils::MiniFunction<bool(double, double)>;
/**
* Synchronously fetch data from the internet
* @param url URL to fetch
* @returns Returned data as bytes, or error on error
*/
2022-12-14 06:11:19 -05:00
GEODE_DLL Result<ByteVector> fetchBytes(std::string const& url);
/**
* 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
2022-10-30 14:59:20 -04:00
* @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(
2022-12-04 11:39:40 -05:00
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
*/
Result<matjson::Value> fetchJSON(std::string const& url);
class SentAsyncWebRequest;
2022-10-30 14:59:20 -04:00
template <class T>
class AsyncWebResult;
class AsyncWebResponse;
class AsyncWebRequest;
2023-02-08 10:25:07 -05:00
using AsyncProgress = utils::MiniFunction<void(SentAsyncWebRequest&, double, double)>;
using AsyncExpect = utils::MiniFunction<void(std::string const&)>;
using AsyncExpectCode = utils::MiniFunction<void(std::string const&, int)>;
using AsyncThen = utils::MiniFunction<void(SentAsyncWebRequest&, ByteVector const&)>;
using AsyncCancelled = utils::MiniFunction<void(SentAsyncWebRequest&)>;
/**
2022-10-30 14:59:20 -04:00
* A handle to an in-progress sent asynchronous web request. Use this to
* cancel the request / query information about it
*/
2023-04-05 14:01:32 -04:00
class GEODE_DLL SentAsyncWebRequest {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
2022-10-30 14:59:20 -04:00
template <class T>
friend class AsyncWebResult;
friend class AsyncWebRequest;
void pause();
void resume();
void error(std::string const& error, int code);
void doCancel();
public:
/**
* Do not call these manually.
*/
SentAsyncWebRequest();
~SentAsyncWebRequest();
static std::shared_ptr<SentAsyncWebRequest> create(AsyncWebRequest const&, std::string const& id);
/**
2022-10-30 14:59:20 -04:00
* Cancel the request. Cleans up any downloaded files, but if you run
* extra code in `then`, you will have to clean it up manually in
* `cancelled`
*/
void cancel();
/**
* Check if the request is finished
*/
bool finished() const;
};
using SentAsyncWebRequestHandle = std::shared_ptr<SentAsyncWebRequest>;
2022-10-30 14:59:20 -04:00
template <class T>
2022-12-14 06:11:19 -05:00
using DataConverter = Result<T> (*)(ByteVector const&);
// Hack until 2.0.0 to store extra members in AsyncWebRequest
struct AsyncWebRequestData {
std::string m_userAgent;
std::string m_customRequest;
bool m_isPostRequest = false;
std::string m_postFields;
bool m_isJsonRequest = false;
2023-09-08 11:02:59 -04:00
bool m_sent = false;
};
/**
2022-10-30 14:59:20 -04:00
* An asynchronous, thread-safe web request. Downloads data from the
* internet without slowing the main thread. All callbacks are run in the
* GD thread, so interacting with the Cocos2d UI is perfectly safe
*/
2022-10-12 21:50:41 -04:00
class GEODE_DLL AsyncWebRequest {
private:
// i want to cry whose idea was to not make this pimpl
// For 2.0.0: make this pimpl
2022-10-12 21:50:41 -04:00
std::optional<std::string> m_joinID;
std::string m_url;
AsyncThen m_then = nullptr;
AsyncExpectCode m_expect = nullptr;
2022-10-12 21:50:41 -04:00
AsyncProgress m_progress = nullptr;
AsyncCancelled m_cancelled = nullptr;
2023-09-08 11:05:20 -04:00
mutable AsyncWebRequestData* m_extra = nullptr;
2023-09-08 11:02:59 -04:00
std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target;
std::vector<std::string> m_httpHeaders;
2022-10-12 21:50:41 -04:00
AsyncWebRequestData& extra();
AsyncWebRequestData const& extra() const;
2022-10-30 14:59:20 -04:00
template <class T>
2022-10-12 21:50:41 -04:00
friend class AsyncWebResult;
friend class SentAsyncWebRequest;
friend class AsyncWebResponse;
public:
/**
2022-10-30 14:59:20 -04:00
* An asynchronous, thread-safe web request. Downloads data from the
* internet without slowing the main thread. All callbacks are run in the
* GD thread, so interacting with the Cocos2d UI is perfectly safe
*/
AsyncWebRequest() = default;
/**
2022-10-30 14:59:20 -04:00
* If you only want one instance of this web request to run (for example,
* you're downloading some global data for a manager), then use this
* to specify a Join ID. If another request with the same ID is
* already running, this request's callbacks will be appended to the
* existing one instead of creating a new request
2022-10-30 14:59:20 -04:00
* @param requestID The Join ID of the request. Can be anything,
* recommended to be something unique
* @returns Same AsyncWebRequest
*/
2022-10-12 21:50:41 -04:00
AsyncWebRequest& join(std::string const& requestID);
/**
* In order to specify a http header to the request, give it here.
* Can be called more than once.
*/
AsyncWebRequest& header(std::string const& header);
/**
* In order to specify an user agent to the request, give it here.
*/
AsyncWebRequest& userAgent(std::string const& userAgent);
/**
* Specify that the request is a POST request.
*/
AsyncWebRequest& postRequest();
/**
* Specify that the request is a custom request like PUT and DELETE.
*/
AsyncWebRequest& customRequest(std::string const& request);
/**
* Specify the post fields to send with the request. Only valid if
* `postRequest` or `customRequest` was called before.
*/
AsyncWebRequest& postFields(std::string const& fields);
/**
* Specify the post fields to send with the request. Only valid if
* `postRequest` or `customRequest` was called before. Additionally
* sets the content type to application/json.
*/
AsyncWebRequest& postFields(matjson::Value const& fields);
/**
* URL to fetch from the internet asynchronously
2022-10-30 14:59:20 -04:00
* @param url URL of the data to download. Redirects will be
* automatically followed
2023-10-22 12:01:14 -04:00
* @returns An AsyncWebResponse object
*/
2022-10-12 21:50:41 -04:00
AsyncWebResponse fetch(std::string const& url);
/**
* Specify a callback to run if the download fails. The callback is
* always ran in the GD thread, so interacting with UI is safe
* @param handler Callback to run if the download fails
* @returns Same AsyncWebRequest
*/
2022-10-12 21:50:41 -04:00
AsyncWebRequest& expect(AsyncExpect handler);
/**
* Specify a callback to run if the download fails. The callback is
* always ran in the GD thread, so interacting with UI is safe
* @param handler Callback to run if the download fails
* @returns Same AsyncWebRequest
*/
AsyncWebRequest& expect(AsyncExpectCode handler);
/**
* Specify a callback to run when the download progresses. The callback is
* always ran in the GD thread, so interacting with UI is safe
* @param handler Callback to run when the download progresses
* @returns Same AsyncWebRequest
*/
AsyncWebRequest& progress(AsyncProgress handler);
/**
* Specify a callback to run if the download is cancelled. The callback is
* always ran in the GD thread, so interacting with UI is safe. 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` in this handler
* @param handler Callback to run if the download is cancelled
* @returns Same AsyncWebRequest
*/
AsyncWebRequest& cancelled(AsyncCancelled handler);
/**
2022-10-30 14:59:20 -04:00
* Begin the web request. It's not always necessary to call this as the
* destructor calls it automatically, but if you need access to the
* handle of the sent request, use this
* @returns Handle to the sent web request
*/
2022-10-12 21:50:41 -04:00
SentAsyncWebRequestHandle send();
~AsyncWebRequest();
};
2022-10-30 14:59:20 -04:00
template <class T>
class AsyncWebResult {
private:
AsyncWebRequest& m_request;
DataConverter<T> m_converter;
2022-10-30 14:59:20 -04:00
AsyncWebResult(AsyncWebRequest& request, DataConverter<T> converter) :
m_request(request), m_converter(converter) {}
friend class AsyncWebResponse;
public:
/**
2022-10-30 14:59:20 -04:00
* Specify a callback to run after a download is finished. Runs in the
* GD thread, so interacting with UI is safe
* @param handle Callback to run
2022-10-30 14:59:20 -04:00
* @returns The original AsyncWebRequest, where you can specify more
* aspects about the request like failure and progress callbacks
*/
2023-02-08 10:25:07 -05:00
AsyncWebRequest& then(utils::MiniFunction<void(T)> handle);
/**
2022-10-30 14:59:20 -04:00
* Specify a callback to run after a download is finished. Runs in the
* GD thread, so interacting with UI is safe
* @param handle Callback to run
2022-10-30 14:59:20 -04:00
* @returns The original AsyncWebRequest, where you can specify more
* aspects about the request like failure and progress callbacks
*/
2023-02-08 10:25:07 -05:00
AsyncWebRequest& then(utils::MiniFunction<void(SentAsyncWebRequest&, T)> handle);
};
class GEODE_DLL AsyncWebResponse {
private:
AsyncWebRequest& m_request;
inline AsyncWebResponse(AsyncWebRequest& request) : m_request(request) {}
friend class AsyncWebRequest;
public:
/**
2022-10-30 14:59:20 -04:00
* Download into a stream. Make sure the stream lives for the entire
* duration of the request. If you want to download a file, use the
* `ghc::filesystem::path` overload of `into` instead
2022-10-30 14:59:20 -04:00
* @param stream Stream to download into. Make sure it lives long
* enough, otherwise the web request will crash
2022-10-30 14:59:20 -04:00
* @returns AsyncWebResult, where you can specify the `then` action for
* after the download is finished. The result has a `std::monostate`
* template parameter, as it can be assumed you know what you passed
* into `into`
*/
AsyncWebResult<std::monostate> into(std::ostream& stream);
/**
* Download into a file
2022-10-30 14:59:20 -04:00
* @param path File to download into. If it already exists, it will
* be overwritten.
2022-10-30 14:59:20 -04:00
* @returns AsyncWebResult, where you can specify the `then` action for
* after the download is finished. The result has a `std::monostate`
* template parameter, as it can be assumed you know what you passed
* into `into`
*/
AsyncWebResult<std::monostate> into(ghc::filesystem::path const& path);
/**
* Download into memory as a string
2022-10-30 14:59:20 -04:00
* @returns AsyncWebResult, where you can specify the `then` action for
* after the download is finished
*/
AsyncWebResult<std::string> text();
/**
* Download into memory as a byte array
2022-10-30 14:59:20 -04:00
* @returns AsyncWebResult, where you can specify the `then` action for
* after the download is finished
*/
2022-12-14 06:11:19 -05:00
AsyncWebResult<ByteVector> bytes();
/**
* Download into memory as JSON
2022-10-30 14:59:20 -04:00
* @returns AsyncWebResult, where you can specify the `then` action for
* after the download is finished
*/
AsyncWebResult<matjson::Value> json();
/**
2022-10-30 14:59:20 -04:00
* Download into memory as a custom type. The data will first be
* downloaded into memory as a byte array, and then converted using
* the specified converter function
2022-10-30 14:59:20 -04:00
* @param converter Function that converts the data from a byte array
* to the desired type
2022-10-30 14:59:20 -04:00
* @returns AsyncWebResult, where you can specify the `then` action for
* after the download is finished
*/
2022-10-30 14:59:20 -04:00
template <class T>
AsyncWebResult<T> as(DataConverter<T> converter) {
return AsyncWebResult(m_request, converter);
}
};
2022-10-30 14:59:20 -04:00
template <class T>
2023-02-08 10:25:07 -05:00
AsyncWebRequest& AsyncWebResult<T>::then(utils::MiniFunction<void(T)> handle) {
2022-10-30 14:59:20 -04:00
m_request.m_then = [converter = m_converter,
2022-12-14 06:11:19 -05:00
handle](SentAsyncWebRequest& req, ByteVector const& arr) {
2022-10-13 09:36:36 -04:00
auto conv = converter(arr);
if (conv) {
handle(conv.unwrap());
2022-10-30 14:59:20 -04:00
}
else {
req.error("Unable to convert value: " + conv.unwrapErr(), -1);
2022-10-13 09:36:36 -04:00
}
};
return m_request;
}
2022-10-30 14:59:20 -04:00
template <class T>
2023-02-08 10:25:07 -05:00
AsyncWebRequest& AsyncWebResult<T>::then(utils::MiniFunction<void(SentAsyncWebRequest&, T)> handle) {
2022-10-30 14:59:20 -04:00
m_request.m_then = [converter = m_converter,
2022-12-14 06:11:19 -05:00
handle](SentAsyncWebRequest& req, ByteVector const& arr) {
2022-10-13 09:36:36 -04:00
auto conv = converter(arr);
if (conv) {
handle(req, conv.value());
2022-10-30 14:59:20 -04:00
}
else {
req.error("Unable to convert value: " + conv.error(), -1);
2022-10-13 09:36:36 -04:00
}
};
return m_request;
}
}