#pragma once #include "../DefaultInclude.hpp" #include #include "Result.hpp" #include "json.hpp" #include namespace geode::utils::web { using FileProgressCallback = std::function; /** * Synchronously fetch data from the internet * @param url URL to fetch * @returns Returned data as bytes, or error on error */ GEODE_DLL Result 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 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 Result 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()); } } class SentAsyncWebRequest; template class AsyncWebResult; class AsyncWebResponse; class AsyncWebRequest; using AsyncProgress = std::function; using AsyncExpect = std::function; using AsyncThen = std::function; using AsyncCancelled = std::function; class SentAsyncWebRequest { private: std::string m_id; std::string m_url; std::vector m_thens; std::vector m_expects; std::vector m_progresses; std::vector m_cancelleds; std::atomic m_paused = true; std::atomic m_cancelled = false; std::atomic m_finished = false; std::atomic m_cleanedUp = false; mutable std::mutex m_mutex; std::variant< std::monostate, std::ostream*, ghc::filesystem::path > m_target = std::monostate(); template 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; template using DataConverter = Result(*)(byte_array const&); class GEODE_DLL AsyncWebRequest { private: std::optional 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 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(); }; template class AsyncWebResult { private: AsyncWebRequest& m_request; DataConverter m_converter; AsyncWebResult(AsyncWebRequest& request, DataConverter converter) : m_request(request), m_converter(converter) {} friend class AsyncWebResponse; public: AsyncWebRequest& then(std::function handle); AsyncWebRequest& then(std::function handle); }; 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 into(std::ostream& stream); AsyncWebResult into(ghc::filesystem::path const& path); AsyncWebResult text(); AsyncWebResult bytes(); AsyncWebResult json(); template AsyncWebResult as(DataConverter converter) { return AsyncWebResult(m_request, converter); } }; template AsyncWebRequest& AsyncWebResult::then(std::function 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 AsyncWebRequest& AsyncWebResult::then(std::function 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; } }