#pragma once #include #include #include #include "Common/File/Path.h" #include "Common/Net/NetBuffer.h" namespace http { enum class RequestMethod { GET, POST, }; enum class RequestFlags { Default = 0, ProgressBar = 1, ProgressBarDelayed = 2, Cached24H = 4, KeepInMemory = 8, }; ENUM_CLASS_BITOPS(RequestFlags); // Abstract request. class Request { public: Request(RequestMethod method, std::string_view url, std::string_view name, bool *cancelled, RequestFlags mode); virtual ~Request() {} void SetAccept(const char *mime) { acceptMime_ = mime; } void SetUserAgent(std::string_view userAgent) { userAgent_ = userAgent; } // NOTE: Completion callbacks (which these are) are deferred until RunCallback is called. This is so that // the call will end up on the thread that calls g_DownloadManager.Update(). void SetCallback(std::function callback) { callback_ = callback; } void RunCallback() { if (callback_) { callback_(*this); } hasRunCallback_ = true; } virtual void Start() = 0; virtual void Join() = 0; virtual bool Done() = 0; virtual bool Failed() const = 0; virtual bool HasRunCallback() { return Done() && hasRunCallback_; } // Returns 1.0 when done. That one value can be compared exactly - or just use Done(). float Progress() const { return progress_.progress; } float SpeedKBps() const { return progress_.kBps; } std::string url() const { return url_; } const Path &OutFile() const { return outfile_; } void OverrideOutFile(const Path &path) { outfile_ = path; } void AddFlag(RequestFlags flag) { flags_ |= flag; } void Cancel() { cancelled_ = true; } bool IsCancelled() const { return cancelled_; } // If not downloading to a file, access this to get the result. Buffer &buffer() { return buffer_; } const Buffer &buffer() const { return buffer_; } // NOTE! The value of ResultCode is INVALID until Done() returns true. int ResultCode() const { return resultCode_; } protected: RequestMethod method_; std::string url_; std::string name_; const char *acceptMime_ = "*/*"; std::string userAgent_; Path outfile_; Buffer buffer_; bool cancelled_ = false; int resultCode_ = 0; bool hasRunCallback_ = false; std::vector responseHeaders_; net::RequestProgress progress_; RequestFlags flags_; private: std::function callback_; }; using std::shared_ptr; class RequestManager { public: ~RequestManager() { CancelAll(); } // NOTE: This is the only version that supports the cache flag (for now). std::shared_ptr StartDownload(std::string_view url, const Path &outfile, RequestFlags flags, const char *acceptMime = nullptr, std::string_view name = "", std::function callback = {}); std::shared_ptr AsyncPostWithCallback( std::string_view url, std::string_view postData, std::string_view postMime, // Use postMime = "application/x-www-form-urlencoded" for standard form-style posts, such as used by retroachievements. For encoding form data manually we have MultipartFormDataEncoder. RequestFlags flags, std::function callback, std::string_view name = ""); // Drops finished downloads from the list. void Update(); void CancelAll(); void SetUserAgent(std::string_view userAgent) { userAgent_ = userAgent; } void SetCacheDir(const Path &path) { cacheDir_ = path; } Path UrlToCachePath(const std::string_view url); bool ReadFileFromCache(std::string_view url, std::string *data); private: std::vector> downloads_; // These get copied to downloads_ in Update(). It's so that callbacks can add new downloads // while running. std::vector> newDownloads_; std::string userAgent_; Path cacheDir_; }; inline const char *RequestMethodToString(RequestMethod method) { switch (method) { case RequestMethod::GET: return "GET"; case RequestMethod::POST: return "POST"; default: return "N/A"; } } } // namespace net