/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2020 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #ifndef __OPENSPACE_CORE___HTTPREQUEST___H__ #define __OPENSPACE_CORE___HTTPREQUEST___H__ #include #include #include #include #include #include #include #include // @TODO: This class is a diamond-of-death; maybe some redesign to make the things into // components, rather than inheritance? #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4250) #endif namespace openspace { namespace curlfunctions { size_t writeCallback(char* ptr, size_t size, size_t nmemb, void* userData); int progressCallback(void* userData, int64_t nTotalDownloadBytes, int64_t nDownloadedBytes, int64_t nTotalUploadBytes, int64_t nUploadBytes); size_t headerCallback(char* ptr, size_t size, size_t nmemb, void* userData); } // Synchronous http request class HttpRequest { public: enum class ReadyState : unsigned int { Unsent, Loading, ReceivedHeader, Fail, Success }; struct Progress { bool totalBytesKnown = false; size_t totalBytes = 0; size_t downloadedBytes = 0; }; struct Data { char* buffer; size_t size; }; struct Header { char* buffer; size_t size; }; using ReadyStateChangeCallback = std::function; // ProgressCallback: Return non-zero value to cancel download. using ProgressCallback = std::function; // DataCallback: Return number of bytes successfully stored. // If this does not match the data buffer sice reported in Data, // the request will fail. using DataCallback = std::function; // HeaderCallback: Return number of bytes successfully stored. // If this does not match the data buffer sice reported in Header, // the request will fail. using HeaderCallback = std::function; struct RequestOptions { int requestTimeoutSeconds; // 0 for no timeout }; HttpRequest(std::string url); void onReadyStateChange(ReadyStateChangeCallback cb); void onHeader(HeaderCallback cb); void onProgress(ProgressCallback cb); void onData(DataCallback cb); void perform(RequestOptions opt); const std::string& url() const; private: void setReadyState(ReadyState state); std::string _url; ReadyStateChangeCallback _onReadyStateChange; ProgressCallback _onProgress; DataCallback _onData; HeaderCallback _onHeader; ReadyState _readyState; Progress _progress; friend size_t curlfunctions::writeCallback(char* ptr, size_t size, size_t nmemb, void* userData); friend int curlfunctions::progressCallback(void* userData, int64_t nTotalDownloadBytes, int64_t nDownloadedBytes, int64_t nTotalUploadBytes, int64_t nUploadBytes); friend size_t curlfunctions::headerCallback(char* ptr, size_t size, size_t nmemb, void* userData); }; class HttpDownload { public: using ProgressCallback = std::function; using HeaderCallback = std::function; HttpDownload(); HttpDownload(HttpDownload&& d) = default; HttpDownload& operator=(HttpDownload&&) = default; virtual ~HttpDownload() = default; void onProgress(ProgressCallback progressCallback); void onHeader(HeaderCallback headerCallback); bool hasStarted() const; bool hasFailed() const; bool hasSucceeded() const; protected: virtual size_t handleData(HttpRequest::Data d) = 0; virtual bool initDownload() = 0; virtual bool deinitDownload() = 0; bool callOnProgress(HttpRequest::Progress p); void markAsStarted(); void markAsSuccessful(); void markAsFailed(); private: ProgressCallback _onProgress; bool _started = false; bool _failed = false; bool _successful = false; }; class SyncHttpDownload : public virtual HttpDownload { public: SyncHttpDownload(std::string url); SyncHttpDownload(SyncHttpDownload&& d) = default; SyncHttpDownload& operator=(SyncHttpDownload&&) = default; virtual ~SyncHttpDownload() = default; void download(HttpRequest::RequestOptions opt); const std::string& url() const; protected: HttpRequest _httpRequest; }; class AsyncHttpDownload : public virtual HttpDownload { public: AsyncHttpDownload(std::string url); AsyncHttpDownload(AsyncHttpDownload&& d); AsyncHttpDownload& operator=(AsyncHttpDownload&&) = default; virtual ~AsyncHttpDownload() = default; void start(HttpRequest::RequestOptions opt); void cancel(); void wait(); const std::string& url() const; protected: void download(HttpRequest::RequestOptions opt); private: HttpRequest _httpRequest; std::thread _downloadThread; std::mutex _conditionMutex; std::condition_variable _downloadFinishCondition; bool _shouldCancel = false; std::mutex _stateChangeMutex; }; class HttpFileDownload : public virtual HttpDownload { public: BooleanType(Overwrite); HttpFileDownload() = default; HttpFileDownload(std::string destination, Overwrite = Overwrite::No); HttpFileDownload(HttpFileDownload&& d) = default; HttpFileDownload& operator=(HttpFileDownload&&) = default; virtual ~HttpFileDownload() = default; const std::string& destination() const; protected: bool initDownload() override; bool deinitDownload() override; size_t handleData(HttpRequest::Data d) override; static std::mutex _directoryCreationMutex; std::atomic_bool _hasHandle = false; private: std::string _destination; bool _overwrite; std::ofstream _file; static const int MaxFilehandles = 35; static std::atomic_int nCurrentFilehandles; }; class HttpMemoryDownload : public virtual HttpDownload { public: HttpMemoryDownload() = default; HttpMemoryDownload(HttpMemoryDownload&& d) = default; HttpMemoryDownload& operator=(HttpMemoryDownload&&) = default; virtual ~HttpMemoryDownload() = default; const std::vector& downloadedData() const; protected: bool initDownload() override; bool deinitDownload() override; size_t handleData(HttpRequest::Data d) override; private: std::vector _downloadedData; }; // Synchronous download to memory class SyncHttpMemoryDownload : public SyncHttpDownload, public HttpMemoryDownload { public: SyncHttpMemoryDownload(std::string url); virtual ~SyncHttpMemoryDownload() = default; }; // Synchronous download to file class SyncHttpFileDownload : public SyncHttpDownload, public HttpFileDownload { public: SyncHttpFileDownload( std::string url, std::string destinationPath, HttpFileDownload::Overwrite = Overwrite::No ); virtual ~SyncHttpFileDownload() = default; }; // Asynchronous download to memory class AsyncHttpMemoryDownload : public AsyncHttpDownload, public HttpMemoryDownload { public: AsyncHttpMemoryDownload(std::string url); virtual ~AsyncHttpMemoryDownload() = default; }; // Asynchronous download to file class AsyncHttpFileDownload : public AsyncHttpDownload, public HttpFileDownload { public: AsyncHttpFileDownload( std::string url, std::string destinationPath, HttpFileDownload::Overwrite = Overwrite::No ); virtual ~AsyncHttpFileDownload() = default; }; } // namespace openspace #ifdef _MSC_VER #pragma warning(pop) #endif #endif // __OPENSPACE_CORE___HTTPREQUEST___H__